@canonmsg/agent-sdk 0.3.0 → 0.5.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/README.md +3 -1
- package/dist/canon-agent.d.ts +5 -0
- package/dist/canon-agent.js +52 -11
- package/dist/index.d.ts +1 -1
- package/dist/policy-history.d.ts +10 -0
- package/dist/policy-history.js +11 -0
- package/dist/polling.d.ts +3 -1
- package/dist/polling.js +19 -3
- package/dist/realtime.d.ts +6 -0
- package/dist/realtime.js +23 -2
- package/dist/turn-filter.d.ts +8 -2
- package/dist/turn-filter.js +16 -3
- package/dist/types.d.ts +17 -3
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -75,7 +75,7 @@ The `message` event handler receives a context object with:
|
|
|
75
75
|
| `conversation` | `SDKConversation` | Full conversation metadata |
|
|
76
76
|
| `reply` | `(text: string, options?) => Promise<{ messageId: string }>` | Convenience alias for `replyFinal` |
|
|
77
77
|
| `replyFinal` | `(text: string, options?) => Promise<{ messageId: string }>` | Send the durable final reply for a turn |
|
|
78
|
-
| `replyProgress` | `(text: string, options?) => Promise<{ messageId: string }>` |
|
|
78
|
+
| `replyProgress` | `(text: string, options?) => Promise<{ turnId: string; durable: boolean; messageId: string \| null }>` | Update the live turn progress; add `durable: true` to also persist it |
|
|
79
79
|
| `agent` | `AgentContext` | Trusted Canon agent identity and access context |
|
|
80
80
|
| `session` | `SessionInfo \| undefined` | Per-conversation queue/session state when sessions are enabled |
|
|
81
81
|
| `turn` | `TurnController \| undefined` | Live turn-state helpers for thinking/streaming/tool/waiting-input |
|
|
@@ -178,3 +178,5 @@ While a handler runs, the SDK automatically publishes Canon turn state and clear
|
|
|
178
178
|
- `setWaitingInput(text?)`
|
|
179
179
|
|
|
180
180
|
`setWaitingInput()` keeps the turn open in `waiting_input` and optionally sends a control message to the conversation so Canon clients can render “reply to continue” correctly.
|
|
181
|
+
|
|
182
|
+
`replyProgress()` is ephemeral by default: it updates the live RTDB turn preview without adding a permanent Firestore message. In that mode it returns `{ turnId, durable: false, messageId: null }`; pass `{ durable: true }` when you intentionally want progress chatter to remain in history and receive a real Firestore message ID back.
|
package/dist/canon-agent.d.ts
CHANGED
|
@@ -12,6 +12,7 @@ export declare class CanonAgent {
|
|
|
12
12
|
private agentContext;
|
|
13
13
|
private cachedConversationIds;
|
|
14
14
|
private running;
|
|
15
|
+
private runtimeHeartbeatTimer;
|
|
15
16
|
constructor(options: CanonAgentOptions);
|
|
16
17
|
on(event: 'message', handler: MessageHandler): void;
|
|
17
18
|
start(): Promise<void>;
|
|
@@ -28,6 +29,10 @@ export declare class CanonAgent {
|
|
|
28
29
|
attachment: import('@canonmsg/core').MediaAttachment;
|
|
29
30
|
}>;
|
|
30
31
|
stop(): Promise<void>;
|
|
32
|
+
private publishAgentRuntime;
|
|
33
|
+
private startRuntimeHeartbeat;
|
|
34
|
+
private stopRuntimeHeartbeat;
|
|
35
|
+
private clearAgentRuntime;
|
|
31
36
|
private handleMessages;
|
|
32
37
|
private executeHandler;
|
|
33
38
|
static register(options: {
|
package/dist/canon-agent.js
CHANGED
|
@@ -1,17 +1,21 @@
|
|
|
1
|
-
import { CanonClient, initRTDBAuth, writeSessionState, clearSessionState, writeTurnState, clearTurnState, } from '@canonmsg/core';
|
|
1
|
+
import { CanonClient, FINAL_MESSAGE_HANDOFF_MS, initRTDBAuth, rtdbWrite, writeSessionState, clearSessionState, writeTurnState, clearTurnState, } from '@canonmsg/core';
|
|
2
2
|
import { randomUUID } from 'node:crypto';
|
|
3
3
|
import { AuthManager } from './auth.js';
|
|
4
4
|
import { Debouncer } from './debouncer.js';
|
|
5
5
|
import { PollingManager } from './polling.js';
|
|
6
6
|
import { SessionManager } from './session-manager.js';
|
|
7
7
|
const AUTO_MODE_THRESHOLD = 500;
|
|
8
|
+
const AGENT_RUNTIME_HEARTBEAT_MS = 30_000;
|
|
8
9
|
const SDK_RUNTIME_CAPABILITIES = {
|
|
9
10
|
supportsInterrupt: false,
|
|
10
11
|
supportsQueue: true,
|
|
11
12
|
supportsInterleave: false,
|
|
12
13
|
supportsRequiresAction: false,
|
|
13
|
-
supportsNonFinalPermanentMessages:
|
|
14
|
+
supportsNonFinalPermanentMessages: false,
|
|
14
15
|
};
|
|
16
|
+
function sleep(ms) {
|
|
17
|
+
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
18
|
+
}
|
|
15
19
|
export class CanonAgent {
|
|
16
20
|
options;
|
|
17
21
|
apiClient;
|
|
@@ -25,6 +29,7 @@ export class CanonAgent {
|
|
|
25
29
|
agentContext = null;
|
|
26
30
|
cachedConversationIds = [];
|
|
27
31
|
running = false;
|
|
32
|
+
runtimeHeartbeatTimer = null;
|
|
28
33
|
constructor(options) {
|
|
29
34
|
this.options = {
|
|
30
35
|
baseUrl: 'https://api-6m6mlelskq-uc.a.run.app',
|
|
@@ -106,12 +111,16 @@ export class CanonAgent {
|
|
|
106
111
|
rtm.setOnAgentContext((ctx) => {
|
|
107
112
|
this.agentContext = ctx;
|
|
108
113
|
});
|
|
114
|
+
rtm.setConnectionHandlers({
|
|
115
|
+
onConnected: () => this.startRuntimeHeartbeat(),
|
|
116
|
+
onDisconnected: () => this.stopRuntimeHeartbeat(),
|
|
117
|
+
});
|
|
109
118
|
this.realtimeManager = rtm;
|
|
110
119
|
await rtm.start();
|
|
111
120
|
console.log('[canon-sdk] SSE stream started');
|
|
112
121
|
}
|
|
113
122
|
else {
|
|
114
|
-
this.pollingManager = new PollingManager(this.apiClient, this.debouncer, agentId, this.options.pollingIntervalMs);
|
|
123
|
+
this.pollingManager = new PollingManager(this.apiClient, this.debouncer, agentId, this.options.pollingIntervalMs, () => this.startRuntimeHeartbeat(), () => this.stopRuntimeHeartbeat());
|
|
115
124
|
await this.pollingManager.start();
|
|
116
125
|
console.log(`[canon-sdk] Polling started (interval: ${this.options.pollingIntervalMs}ms)`);
|
|
117
126
|
}
|
|
@@ -152,6 +161,7 @@ export class CanonAgent {
|
|
|
152
161
|
clearTurnState(id, this.agentId).catch(() => { });
|
|
153
162
|
}
|
|
154
163
|
}
|
|
164
|
+
await this.clearAgentRuntime();
|
|
155
165
|
this.pollingManager?.stop();
|
|
156
166
|
this.realtimeManager?.stop();
|
|
157
167
|
this.sessionManager?.destroy();
|
|
@@ -159,6 +169,36 @@ export class CanonAgent {
|
|
|
159
169
|
this.debouncer.destroy();
|
|
160
170
|
console.log('[canon-sdk] Stopped');
|
|
161
171
|
}
|
|
172
|
+
async publishAgentRuntime() {
|
|
173
|
+
if (!this.agentId)
|
|
174
|
+
return;
|
|
175
|
+
await rtdbWrite(`/agent-runtime/${this.agentId}`, {
|
|
176
|
+
clientType: this.options.clientType ?? 'generic',
|
|
177
|
+
hostMode: false,
|
|
178
|
+
updatedAt: { '.sv': 'timestamp' },
|
|
179
|
+
});
|
|
180
|
+
}
|
|
181
|
+
startRuntimeHeartbeat() {
|
|
182
|
+
void this.publishAgentRuntime();
|
|
183
|
+
if (this.runtimeHeartbeatTimer)
|
|
184
|
+
return;
|
|
185
|
+
this.runtimeHeartbeatTimer = setInterval(() => {
|
|
186
|
+
void this.publishAgentRuntime();
|
|
187
|
+
}, AGENT_RUNTIME_HEARTBEAT_MS);
|
|
188
|
+
this.runtimeHeartbeatTimer.unref?.();
|
|
189
|
+
}
|
|
190
|
+
stopRuntimeHeartbeat() {
|
|
191
|
+
if (this.runtimeHeartbeatTimer) {
|
|
192
|
+
clearInterval(this.runtimeHeartbeatTimer);
|
|
193
|
+
this.runtimeHeartbeatTimer = null;
|
|
194
|
+
}
|
|
195
|
+
void this.clearAgentRuntime();
|
|
196
|
+
}
|
|
197
|
+
async clearAgentRuntime() {
|
|
198
|
+
if (!this.agentId)
|
|
199
|
+
return;
|
|
200
|
+
await rtdbWrite(`/agent-runtime/${this.agentId}`, null).catch(() => { });
|
|
201
|
+
}
|
|
162
202
|
async handleMessages(conversationId, messages) {
|
|
163
203
|
if (!this.handler) {
|
|
164
204
|
console.warn(`[canon-sdk] No message handler registered — messages for ${conversationId} dropped. Call agent.on('message', handler) before starting.`);
|
|
@@ -251,7 +291,6 @@ export class CanonAgent {
|
|
|
251
291
|
await this.apiClient.setTyping(conversationId, true, 'typing');
|
|
252
292
|
}
|
|
253
293
|
catch { }
|
|
254
|
-
await writeTurn('completed');
|
|
255
294
|
const result = await this.apiClient.sendMessage(conversationId, text, {
|
|
256
295
|
...(options ?? {}),
|
|
257
296
|
metadata: {
|
|
@@ -261,27 +300,29 @@ export class CanonAgent {
|
|
|
261
300
|
turnComplete: true,
|
|
262
301
|
},
|
|
263
302
|
});
|
|
303
|
+
await sleep(FINAL_MESSAGE_HANDOFF_MS);
|
|
264
304
|
try {
|
|
265
305
|
await this.apiClient.setTyping(conversationId, false);
|
|
266
306
|
}
|
|
267
307
|
catch { }
|
|
268
|
-
try {
|
|
269
|
-
await this.apiClient.clearStreaming(conversationId);
|
|
270
|
-
}
|
|
271
|
-
catch { }
|
|
272
308
|
return result;
|
|
273
309
|
};
|
|
274
310
|
const replyProgress = async (text, options) => {
|
|
275
311
|
await setLiveState('streaming', text, 'streaming');
|
|
276
|
-
|
|
277
|
-
|
|
312
|
+
if (!options?.durable) {
|
|
313
|
+
return { turnId, durable: false, messageId: null };
|
|
314
|
+
}
|
|
315
|
+
const { durable: _durable, ...sendOptions } = options;
|
|
316
|
+
const result = await this.apiClient.sendMessage(conversationId, text, {
|
|
317
|
+
...sendOptions,
|
|
278
318
|
metadata: {
|
|
279
|
-
...(
|
|
319
|
+
...(sendOptions.metadata ?? {}),
|
|
280
320
|
turnId,
|
|
281
321
|
turnSemantics: 'progress',
|
|
282
322
|
turnComplete: false,
|
|
283
323
|
},
|
|
284
324
|
});
|
|
325
|
+
return { turnId, durable: true, messageId: result.messageId };
|
|
285
326
|
};
|
|
286
327
|
// Enrich history messages with isOwner
|
|
287
328
|
if (this.agentContext?.ownerId) {
|
package/dist/index.d.ts
CHANGED
|
@@ -3,4 +3,4 @@ export { CanonApiError } from '@canonmsg/core';
|
|
|
3
3
|
export { SessionManager } from './session-manager.js';
|
|
4
4
|
export type { SessionConfig, Session } from './session-manager.js';
|
|
5
5
|
export type { AgentContext, CanonMessage, CanonConversation, SendMessageOptions, CreateConversationOptions, } from '@canonmsg/core';
|
|
6
|
-
export type { SDKMessage, SDKConversation, CanonAgentOptions, MessageHandler, MessageHandlerContext, SessionInfo, SessionOptions, DeliveryMode, } from './types.js';
|
|
6
|
+
export type { SDKMessage, SDKConversation, CanonAgentOptions, MessageHandler, MessageHandlerContext, ProgressMessageOptions, ProgressMessageResult, SessionInfo, SessionOptions, DeliveryMode, } from './types.js';
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { type CanonMessage, type ParticipationHistorySnapshot } from '@canonmsg/core';
|
|
2
|
+
export type { ParticipationHistorySnapshot } from '@canonmsg/core';
|
|
3
|
+
/**
|
|
4
|
+
* Builds message-specific participation history snapshots for backlog delivery.
|
|
5
|
+
*
|
|
6
|
+
* `messages` must be ordered newest-first, matching Canon's `getMessages()`
|
|
7
|
+
* API. Each snapshot is computed from older history only, never from the
|
|
8
|
+
* target message itself or newer messages that had not occurred yet.
|
|
9
|
+
*/
|
|
10
|
+
export declare function buildParticipationHistorySnapshots(messages: CanonMessage[], agentId: string): Map<string, ParticipationHistorySnapshot>;
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { buildParticipationHistorySnapshots as buildSharedParticipationHistorySnapshots, } from '@canonmsg/core';
|
|
2
|
+
/**
|
|
3
|
+
* Builds message-specific participation history snapshots for backlog delivery.
|
|
4
|
+
*
|
|
5
|
+
* `messages` must be ordered newest-first, matching Canon's `getMessages()`
|
|
6
|
+
* API. Each snapshot is computed from older history only, never from the
|
|
7
|
+
* target message itself or newer messages that had not occurred yet.
|
|
8
|
+
*/
|
|
9
|
+
export function buildParticipationHistorySnapshots(messages, agentId) {
|
|
10
|
+
return buildSharedParticipationHistorySnapshots(messages, agentId);
|
|
11
|
+
}
|
package/dist/polling.d.ts
CHANGED
|
@@ -5,10 +5,12 @@ export declare class PollingManager {
|
|
|
5
5
|
private debouncer;
|
|
6
6
|
private agentId;
|
|
7
7
|
private pollingIntervalMs;
|
|
8
|
+
private onHealthy;
|
|
9
|
+
private onUnhealthy;
|
|
8
10
|
private lastSeenTimestamps;
|
|
9
11
|
private pollTimer;
|
|
10
12
|
private running;
|
|
11
|
-
constructor(apiClient: CanonClient, debouncer: Debouncer, agentId: string, pollingIntervalMs: number);
|
|
13
|
+
constructor(apiClient: CanonClient, debouncer: Debouncer, agentId: string, pollingIntervalMs: number, onHealthy?: () => void, onUnhealthy?: () => void);
|
|
12
14
|
start(): Promise<void>;
|
|
13
15
|
private poll;
|
|
14
16
|
private findActiveConversations;
|
package/dist/polling.js
CHANGED
|
@@ -1,17 +1,22 @@
|
|
|
1
|
+
import { buildParticipationHistorySnapshots } from './policy-history.js';
|
|
1
2
|
import { shouldDispatchInboundMessage } from './turn-filter.js';
|
|
2
3
|
export class PollingManager {
|
|
3
4
|
apiClient;
|
|
4
5
|
debouncer;
|
|
5
6
|
agentId;
|
|
6
7
|
pollingIntervalMs;
|
|
8
|
+
onHealthy;
|
|
9
|
+
onUnhealthy;
|
|
7
10
|
lastSeenTimestamps = new Map();
|
|
8
11
|
pollTimer = null;
|
|
9
12
|
running = false;
|
|
10
|
-
constructor(apiClient, debouncer, agentId, pollingIntervalMs) {
|
|
13
|
+
constructor(apiClient, debouncer, agentId, pollingIntervalMs, onHealthy, onUnhealthy) {
|
|
11
14
|
this.apiClient = apiClient;
|
|
12
15
|
this.debouncer = debouncer;
|
|
13
16
|
this.agentId = agentId;
|
|
14
17
|
this.pollingIntervalMs = pollingIntervalMs;
|
|
18
|
+
this.onHealthy = onHealthy ?? null;
|
|
19
|
+
this.onUnhealthy = onUnhealthy ?? null;
|
|
15
20
|
}
|
|
16
21
|
async start() {
|
|
17
22
|
this.running = true;
|
|
@@ -21,6 +26,7 @@ export class PollingManager {
|
|
|
21
26
|
for (const convo of conversations) {
|
|
22
27
|
this.lastSeenTimestamps.set(convo.id, now);
|
|
23
28
|
}
|
|
29
|
+
this.onHealthy?.();
|
|
24
30
|
// Start polling
|
|
25
31
|
this.pollTimer = setInterval(() => this.poll(), this.pollingIntervalMs);
|
|
26
32
|
}
|
|
@@ -29,10 +35,13 @@ export class PollingManager {
|
|
|
29
35
|
return;
|
|
30
36
|
try {
|
|
31
37
|
const conversations = await this.apiClient.getConversations();
|
|
38
|
+
this.onHealthy?.();
|
|
32
39
|
const activeConvos = this.findActiveConversations(conversations);
|
|
33
40
|
await Promise.all(activeConvos.map(async (convo) => {
|
|
34
41
|
try {
|
|
35
|
-
const
|
|
42
|
+
const page = await this.apiClient.getMessagesPage(convo.id, 50);
|
|
43
|
+
const messages = page.messages;
|
|
44
|
+
const participationHistory = buildParticipationHistorySnapshots(messages, this.agentId);
|
|
36
45
|
// Filter to only new messages (after lastSeen, not from self)
|
|
37
46
|
const lastSeen = this.lastSeenTimestamps.get(convo.id) || 0;
|
|
38
47
|
const newMessages = messages.filter((m) => {
|
|
@@ -41,7 +50,13 @@ export class PollingManager {
|
|
|
41
50
|
});
|
|
42
51
|
const dispatchable = await Promise.all(newMessages.map(async (message) => ({
|
|
43
52
|
message,
|
|
44
|
-
allow: await shouldDispatchInboundMessage(convo.id, this.agentId, message
|
|
53
|
+
allow: await shouldDispatchInboundMessage(convo.id, this.agentId, message, {
|
|
54
|
+
conversationType: convo.type,
|
|
55
|
+
behavior: page.behavior,
|
|
56
|
+
recentHumanCount: participationHistory.get(message.id)?.recentHumanCount,
|
|
57
|
+
consecutiveAgentTurns: participationHistory.get(message.id)?.consecutiveAgentTurns,
|
|
58
|
+
currentAgentStreakStartedByHuman: participationHistory.get(message.id)?.currentAgentStreakStartedByHuman,
|
|
59
|
+
}),
|
|
45
60
|
})));
|
|
46
61
|
for (const msg of dispatchable.filter((entry) => entry.allow).map((entry) => entry.message)) {
|
|
47
62
|
this.debouncer.add(convo.id, msg);
|
|
@@ -58,6 +73,7 @@ export class PollingManager {
|
|
|
58
73
|
}));
|
|
59
74
|
}
|
|
60
75
|
catch (err) {
|
|
76
|
+
this.onUnhealthy?.();
|
|
61
77
|
console.error('[canon-sdk] Polling error:', err);
|
|
62
78
|
}
|
|
63
79
|
}
|
package/dist/realtime.d.ts
CHANGED
|
@@ -15,8 +15,14 @@ export declare class RealtimeManager {
|
|
|
15
15
|
private knownConversationIds;
|
|
16
16
|
private discoveryTimer;
|
|
17
17
|
private onAgentContext;
|
|
18
|
+
private onConnected;
|
|
19
|
+
private onDisconnected;
|
|
18
20
|
constructor(apiKey: string, debouncer: Debouncer, agentId: string, streamUrl?: string, apiClient?: CanonClient);
|
|
19
21
|
setOnAgentContext(cb: (ctx: AgentContext) => void): void;
|
|
22
|
+
setConnectionHandlers(handlers: {
|
|
23
|
+
onConnected?: () => void;
|
|
24
|
+
onDisconnected?: () => void;
|
|
25
|
+
}): void;
|
|
20
26
|
start(): Promise<void>;
|
|
21
27
|
stop(): void;
|
|
22
28
|
private discoverNewConversations;
|
package/dist/realtime.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { CanonClient, CanonStream } from '@canonmsg/core';
|
|
2
|
+
import { buildParticipationHistorySnapshots } from './policy-history.js';
|
|
2
3
|
import { shouldDispatchInboundMessage } from './turn-filter.js';
|
|
3
4
|
const DISCOVERY_INTERVAL_MS = 5_000;
|
|
4
5
|
/**
|
|
@@ -16,6 +17,8 @@ export class RealtimeManager {
|
|
|
16
17
|
knownConversationIds = new Set();
|
|
17
18
|
discoveryTimer = null;
|
|
18
19
|
onAgentContext = null;
|
|
20
|
+
onConnected = null;
|
|
21
|
+
onDisconnected = null;
|
|
19
22
|
constructor(apiKey, debouncer, agentId, streamUrl, apiClient) {
|
|
20
23
|
this.debouncer = debouncer;
|
|
21
24
|
this.agentId = agentId;
|
|
@@ -53,6 +56,10 @@ export class RealtimeManager {
|
|
|
53
56
|
},
|
|
54
57
|
onConnected: () => {
|
|
55
58
|
// Reset backoff is handled internally by CanonStream
|
|
59
|
+
this.onConnected?.();
|
|
60
|
+
},
|
|
61
|
+
onDisconnected: () => {
|
|
62
|
+
this.onDisconnected?.();
|
|
56
63
|
},
|
|
57
64
|
onError: (err) => {
|
|
58
65
|
console.error('[canon-sdk] SSE error:', err.message);
|
|
@@ -63,6 +70,10 @@ export class RealtimeManager {
|
|
|
63
70
|
setOnAgentContext(cb) {
|
|
64
71
|
this.onAgentContext = cb;
|
|
65
72
|
}
|
|
73
|
+
setConnectionHandlers(handlers) {
|
|
74
|
+
this.onConnected = handlers.onConnected ?? null;
|
|
75
|
+
this.onDisconnected = handlers.onDisconnected ?? null;
|
|
76
|
+
}
|
|
66
77
|
async start() {
|
|
67
78
|
this.running = true;
|
|
68
79
|
// Snapshot current conversations
|
|
@@ -88,6 +99,7 @@ export class RealtimeManager {
|
|
|
88
99
|
this.discoveryTimer = null;
|
|
89
100
|
}
|
|
90
101
|
this.stream.stop();
|
|
102
|
+
this.onDisconnected?.();
|
|
91
103
|
}
|
|
92
104
|
// ── Conversation discovery ─────────────────────────────────────────
|
|
93
105
|
async discoverNewConversations() {
|
|
@@ -108,10 +120,19 @@ export class RealtimeManager {
|
|
|
108
120
|
// Fetch and deliver pending messages from new conversations
|
|
109
121
|
for (const convoId of newConvoIds) {
|
|
110
122
|
try {
|
|
111
|
-
const
|
|
123
|
+
const conversation = convos.find((item) => item.id === convoId);
|
|
124
|
+
const page = await this.apiClient.getMessagesPage(convoId, 50);
|
|
125
|
+
const messages = page.messages;
|
|
126
|
+
const participationHistory = buildParticipationHistorySnapshots(messages, this.agentId);
|
|
112
127
|
const dispatchable = await Promise.all(messages.map(async (message) => ({
|
|
113
128
|
message,
|
|
114
|
-
allow: await shouldDispatchInboundMessage(convoId, this.agentId, message
|
|
129
|
+
allow: await shouldDispatchInboundMessage(convoId, this.agentId, message, {
|
|
130
|
+
conversationType: conversation?.type ?? 'unknown',
|
|
131
|
+
behavior: page.behavior,
|
|
132
|
+
recentHumanCount: participationHistory.get(message.id)?.recentHumanCount,
|
|
133
|
+
consecutiveAgentTurns: participationHistory.get(message.id)?.consecutiveAgentTurns,
|
|
134
|
+
currentAgentStreakStartedByHuman: participationHistory.get(message.id)?.currentAgentStreakStartedByHuman,
|
|
135
|
+
}),
|
|
115
136
|
})));
|
|
116
137
|
const newMessages = dispatchable
|
|
117
138
|
.filter((entry) => entry.allow)
|
package/dist/turn-filter.d.ts
CHANGED
|
@@ -1,2 +1,8 @@
|
|
|
1
|
-
import { type CanonMessage } from '@canonmsg/core';
|
|
2
|
-
export declare function shouldDispatchInboundMessage(conversationId: string, agentId: string, message: CanonMessage
|
|
1
|
+
import { type ResolvedAgentBehaviorPolicy, type CanonMessage } from '@canonmsg/core';
|
|
2
|
+
export declare function shouldDispatchInboundMessage(conversationId: string, agentId: string, message: CanonMessage, options?: {
|
|
3
|
+
conversationType?: 'direct' | 'group' | 'unknown';
|
|
4
|
+
behavior?: ResolvedAgentBehaviorPolicy | null;
|
|
5
|
+
recentHumanCount?: number;
|
|
6
|
+
consecutiveAgentTurns?: number;
|
|
7
|
+
currentAgentStreakStartedByHuman?: boolean;
|
|
8
|
+
}): Promise<boolean>;
|
package/dist/turn-filter.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { normalizeTurnState, rtdbRead, shouldTriggerAgentTurn, } from '@canonmsg/core';
|
|
1
|
+
import { evaluateParticipationPolicy, normalizeTurnState, rtdbRead, shouldTriggerAgentTurn, } from '@canonmsg/core';
|
|
2
2
|
function normalizeRuntimeTurnState(value) {
|
|
3
3
|
const turnState = normalizeTurnState(value);
|
|
4
4
|
if (turnState) {
|
|
@@ -15,7 +15,7 @@ function normalizeRuntimeTurnState(value) {
|
|
|
15
15
|
}
|
|
16
16
|
return null;
|
|
17
17
|
}
|
|
18
|
-
export async function shouldDispatchInboundMessage(conversationId, agentId, message) {
|
|
18
|
+
export async function shouldDispatchInboundMessage(conversationId, agentId, message, options) {
|
|
19
19
|
if (message.senderId === agentId)
|
|
20
20
|
return false;
|
|
21
21
|
let senderTurnState = null;
|
|
@@ -30,9 +30,22 @@ export async function shouldDispatchInboundMessage(conversationId, agentId, mess
|
|
|
30
30
|
catch {
|
|
31
31
|
senderTurnState = null;
|
|
32
32
|
}
|
|
33
|
-
|
|
33
|
+
const triggerDecision = shouldTriggerAgentTurn({
|
|
34
34
|
senderType: message.senderType,
|
|
35
35
|
metadata: message.metadata,
|
|
36
36
|
senderTurnState,
|
|
37
|
+
});
|
|
38
|
+
if (!triggerDecision.allow)
|
|
39
|
+
return false;
|
|
40
|
+
if (!options?.behavior)
|
|
41
|
+
return true;
|
|
42
|
+
return evaluateParticipationPolicy(options.behavior, {
|
|
43
|
+
conversationType: options.conversationType ?? 'unknown',
|
|
44
|
+
senderType: message.senderType,
|
|
45
|
+
isOwner: message.isOwner,
|
|
46
|
+
mentionedAgent: Array.isArray(message.mentions) && message.mentions.includes(agentId),
|
|
47
|
+
recentHumanCount: options.recentHumanCount,
|
|
48
|
+
consecutiveAgentTurns: options.consecutiveAgentTurns,
|
|
49
|
+
currentAgentStreakStartedByHuman: options.currentAgentStreakStartedByHuman,
|
|
37
50
|
}).allow;
|
|
38
51
|
}
|
package/dist/types.d.ts
CHANGED
|
@@ -2,6 +2,22 @@ export type { AgentClientType, CanonMessage, CanonConversation, AgentContext, Se
|
|
|
2
2
|
import type { CanonMessage, CanonConversation, SendMessageOptions } from '@canonmsg/core';
|
|
3
3
|
export type SDKMessage = CanonMessage;
|
|
4
4
|
export type SDKConversation = CanonConversation;
|
|
5
|
+
export interface ProgressMessageOptions extends SendMessageOptions {
|
|
6
|
+
/**
|
|
7
|
+
* Persist the progress update to Firestore.
|
|
8
|
+
* By default, progress stays ephemeral and only updates the live RTDB turn state.
|
|
9
|
+
*/
|
|
10
|
+
durable?: boolean;
|
|
11
|
+
}
|
|
12
|
+
export type ProgressMessageResult = {
|
|
13
|
+
turnId: string;
|
|
14
|
+
durable: false;
|
|
15
|
+
messageId: null;
|
|
16
|
+
} | {
|
|
17
|
+
turnId: string;
|
|
18
|
+
durable: true;
|
|
19
|
+
messageId: string;
|
|
20
|
+
};
|
|
5
21
|
export interface SessionInfo {
|
|
6
22
|
/** Session ID (= conversationId) */
|
|
7
23
|
id: string;
|
|
@@ -30,9 +46,7 @@ export interface MessageHandlerContext {
|
|
|
30
46
|
replyFinal: (text: string, options?: SendMessageOptions) => Promise<{
|
|
31
47
|
messageId: string;
|
|
32
48
|
}>;
|
|
33
|
-
replyProgress: (text: string, options?:
|
|
34
|
-
messageId: string;
|
|
35
|
-
}>;
|
|
49
|
+
replyProgress: (text: string, options?: ProgressMessageOptions) => Promise<ProgressMessageResult>;
|
|
36
50
|
/** Soft-delete a message (agent must be the sender) */
|
|
37
51
|
deleteMessage: (messageId: string) => Promise<void>;
|
|
38
52
|
/** Mark conversation as read */
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@canonmsg/agent-sdk",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.5.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",
|
|
@@ -23,7 +23,7 @@
|
|
|
23
23
|
"node": ">=18.0.0"
|
|
24
24
|
},
|
|
25
25
|
"dependencies": {
|
|
26
|
-
"@canonmsg/core": "^0.
|
|
26
|
+
"@canonmsg/core": "^0.5.0"
|
|
27
27
|
},
|
|
28
28
|
"publishConfig": {
|
|
29
29
|
"access": "public"
|