@canonmsg/codex-plugin 0.9.8 → 0.11.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 +6 -2
- package/dist/host.js +108 -32
- package/dist/inbound-policy.d.ts +3 -1
- package/dist/inbound-policy.js +4 -1
- package/dist/register.js +3 -1
- package/dist/session-store.js +15 -0
- package/package.json +3 -3
package/README.md
CHANGED
|
@@ -22,7 +22,9 @@ canon-codex --cwd /path/to/project
|
|
|
22
22
|
|
|
23
23
|
Registration saves a Canon profile in `~/.canon/agents.json`, the same shared profile store used by the Claude Code integration and supported by the OpenClaw plugin.
|
|
24
24
|
|
|
25
|
-
|
|
25
|
+
`canon-codex` is the local agent process. Keep that terminal open while you want Canon to reach the agent. Closing it, logging out, rebooting, or sleeping long enough to stop the process takes the local agent offline until you revive it.
|
|
26
|
+
|
|
27
|
+
Install `@canonmsg/local-agents` and run `canon-necromance` to list every recorded local agent from newest to oldest, then revive one in the foreground:
|
|
26
28
|
|
|
27
29
|
```bash
|
|
28
30
|
npm install -g @canonmsg/local-agents
|
|
@@ -30,7 +32,9 @@ canon-necromance
|
|
|
30
32
|
canon-necromance revive my-codex
|
|
31
33
|
```
|
|
32
34
|
|
|
33
|
-
Do not run registration again unless Canon tells you the saved API key is invalid. If you registered multiple profiles, relaunch the same one with `CANON_AGENT=<profile> canon-codex --cwd /path/to/project`.
|
|
35
|
+
Do not run registration again unless Canon tells you the saved API key is invalid. If you registered multiple profiles, relaunch the same one with `CANON_AGENT=<profile> canon-codex --cwd /path/to/project`.
|
|
36
|
+
|
|
37
|
+
Public docs: <https://canonmail.com/agents/integrations>. Coding-host concepts: <https://canonmail.com/agents/coding-agents>.
|
|
34
38
|
|
|
35
39
|
You do not need a git repo for host mode. The plugin passes `--skip-git-repo-check` to Codex, so any readable working directory is valid.
|
|
36
40
|
|
package/dist/host.js
CHANGED
|
@@ -4,7 +4,7 @@ import { randomUUID } from 'node:crypto';
|
|
|
4
4
|
import { dirname } from 'node:path';
|
|
5
5
|
import { parseArgs } from 'node:util';
|
|
6
6
|
import { getCodexImagePath, materializeMessageMedia, } from '@canonmsg/agent-sdk';
|
|
7
|
-
import { buildCanonHostPrompt, buildConfiguredWorkspaceOptionsWithRoots, buildFirstPartyCodingRuntimeDescriptor, buildHydratedInboundContext, buildPublicWorkspaceRoots, buildPublicWorkspaceOptions, createConversationMetadataLoader, createRuntimeStatePublisher, EXECUTION_ENVIRONMENT_MODES, ExecutionEnvironmentError, CanonClient, CanonStream, DEFAULT_PARTICIPATION_HISTORY_FETCH_LIMIT, DEFAULT_RUNTIME_CAPABILITIES, FINAL_MESSAGE_HANDOFF_MS, getActiveProfileLock, initRTDBAuth, buildLocalRuntimeId, heartbeatLocalRuntimeEntry, loadRuntimeSessionState, markLocalRuntimeStopped, normalizeTurnMetadata, normalizeTurnState, prepareConversationEnvironment, loadHostSessionConfig, releaseConversationEnvironment, resolveCanonAgent, rtdbRead, rtdbWrite, shouldTriggerAgentTurn, saveRuntimeSessionState, publishHostAgentRuntime, publishHostSessionSnapshots, renderCanonHostInboundContent, resolveHostWorkspaceCwd, upsertLocalRuntimeEntry, } from '@canonmsg/core';
|
|
7
|
+
import { RUNTIME_NEW_SESSION_ACTION, RUNTIME_STOP_ACTION, RUNTIME_STOP_AND_DROP_ACTION, buildCanonHostPrompt, buildConfiguredWorkspaceOptionsWithRoots, buildFirstPartyCodingRuntimeDescriptor, buildHydratedInboundContext, diffCanonMemberIds, buildPublicWorkspaceRoots, buildPublicWorkspaceOptions, createConversationMetadataLoader, createRuntimeStatePublisher, EXECUTION_ENVIRONMENT_MODES, ExecutionEnvironmentError, CanonClient, CanonStream, DEFAULT_PARTICIPATION_HISTORY_FETCH_LIMIT, DEFAULT_RUNTIME_CAPABILITIES, FINAL_MESSAGE_HANDOFF_MS, getActiveProfileLock, initRTDBAuth, buildLocalRuntimeId, heartbeatLocalRuntimeEntry, loadRuntimeSessionState, markLocalRuntimeStopped, normalizeTurnMetadata, normalizeTurnState, prepareConversationEnvironment, loadHostSessionConfig, releaseConversationEnvironment, resolveCanonAgent, rtdbRead, rtdbWrite, shouldTriggerAgentTurn, saveRuntimeSessionState, publishHostAgentRuntime, publishHostSessionSnapshots, renderCanonHostInboundContent, resolveHostWorkspaceCwd, upsertLocalRuntimeEntry, } from '@canonmsg/core';
|
|
8
8
|
import { buildInboundContextLines, decideAutoReply, } from './inbound-policy.js';
|
|
9
9
|
import { CodexConversationAdapter, } from './adapter.js';
|
|
10
10
|
import { clearStoredThreadId, buildCodexThreadPolicyFingerprint, loadStoredThreadId, saveStoredThreadId, } from './session-store.js';
|
|
@@ -38,7 +38,10 @@ EXAMPLES
|
|
|
38
38
|
canon-codex --cwd ~/dev/canon
|
|
39
39
|
canon-codex --cwd ~/dev/canon --workspace-root ~/dev --full-auto
|
|
40
40
|
|
|
41
|
-
Keep this terminal open while you want Canon to reach the agent
|
|
41
|
+
Keep this terminal open while you want Canon to reach the agent. Closing it,
|
|
42
|
+
logging out, rebooting, or sleeping long enough to stop the process takes the
|
|
43
|
+
local agent offline until you revive it. Docs:
|
|
44
|
+
https://canonmail.com/agents/integrations`;
|
|
42
45
|
const MAX_SESSIONS = 12;
|
|
43
46
|
const IDLE_TIMEOUT_MS = 30 * 60 * 1000;
|
|
44
47
|
const HEARTBEAT_MS = 30_000;
|
|
@@ -65,26 +68,9 @@ function buildCodexRuntimeDescriptor(input) {
|
|
|
65
68
|
defaultPermissionMode: input.defaultPermissionMode,
|
|
66
69
|
streamingTextMode: 'snapshot',
|
|
67
70
|
actions: [
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
description: 'Interrupt the current Codex exec turn.',
|
|
72
|
-
aliases: ['stop'],
|
|
73
|
-
category: 'turn',
|
|
74
|
-
placements: ['composer_slash', 'command_palette'],
|
|
75
|
-
availability: ['busy'],
|
|
76
|
-
dispatch: { kind: 'signal', signal: 'interrupt' },
|
|
77
|
-
},
|
|
78
|
-
{
|
|
79
|
-
id: 'stop-and-clear-queue',
|
|
80
|
-
label: 'Stop & clear queue',
|
|
81
|
-
description: 'Interrupt the current Codex exec turn and drop queued Canon messages.',
|
|
82
|
-
aliases: ['stop-clear', 'clear-queue'],
|
|
83
|
-
category: 'turn',
|
|
84
|
-
placements: ['composer_slash', 'command_palette', 'session_strip'],
|
|
85
|
-
availability: ['busy_with_queue'],
|
|
86
|
-
dispatch: { kind: 'signal', signal: 'stop_and_drop' },
|
|
87
|
-
},
|
|
71
|
+
RUNTIME_STOP_ACTION,
|
|
72
|
+
RUNTIME_STOP_AND_DROP_ACTION,
|
|
73
|
+
RUNTIME_NEW_SESSION_ACTION,
|
|
88
74
|
],
|
|
89
75
|
});
|
|
90
76
|
if (input.models.length > 0) {
|
|
@@ -261,10 +247,12 @@ export async function main() {
|
|
|
261
247
|
initRTDBAuth(client);
|
|
262
248
|
let agentId;
|
|
263
249
|
let ownerId = null;
|
|
250
|
+
let ownerName = null;
|
|
264
251
|
try {
|
|
265
252
|
const ctx = await client.getAgentMe();
|
|
266
253
|
agentId = ctx.agentId;
|
|
267
254
|
ownerId = ctx.ownerId;
|
|
255
|
+
ownerName = ctx.ownerName;
|
|
268
256
|
console.error(`[canon-codex] Connected as ${ctx.displayName || agentId}`);
|
|
269
257
|
}
|
|
270
258
|
catch {
|
|
@@ -314,6 +302,8 @@ export async function main() {
|
|
|
314
302
|
const pendingSessionCreations = new Map();
|
|
315
303
|
const conversationCache = new Map();
|
|
316
304
|
const knownConversationIds = new Set();
|
|
305
|
+
const promptedGroupContextConversationIds = new Set();
|
|
306
|
+
const pendingMembershipChanges = new Map();
|
|
317
307
|
let lastKnownConversationRefreshAt = 0;
|
|
318
308
|
const { getConversationMeta } = createConversationMetadataLoader({
|
|
319
309
|
client,
|
|
@@ -334,6 +324,45 @@ export async function main() {
|
|
|
334
324
|
}
|
|
335
325
|
lastKnownConversationRefreshAt = Date.now();
|
|
336
326
|
}
|
|
327
|
+
function handleConversationUpdated(payload) {
|
|
328
|
+
const rawMemberIds = payload.changes.memberIds;
|
|
329
|
+
if (!Array.isArray(rawMemberIds))
|
|
330
|
+
return;
|
|
331
|
+
const memberIds = rawMemberIds.filter((id) => typeof id === 'string');
|
|
332
|
+
const cached = conversationCache.get(payload.conversationId);
|
|
333
|
+
const membershipChange = payload.membershipChange
|
|
334
|
+
?? (cached ? diffCanonMemberIds(cached.memberIds, memberIds) : null);
|
|
335
|
+
if (cached) {
|
|
336
|
+
conversationCache.set(payload.conversationId, {
|
|
337
|
+
...cached,
|
|
338
|
+
memberIds,
|
|
339
|
+
});
|
|
340
|
+
}
|
|
341
|
+
if (membershipChange) {
|
|
342
|
+
pendingMembershipChanges.set(payload.conversationId, membershipChange);
|
|
343
|
+
}
|
|
344
|
+
if (!memberIds.includes(agentId)) {
|
|
345
|
+
knownConversationIds.delete(payload.conversationId);
|
|
346
|
+
conversationCache.delete(payload.conversationId);
|
|
347
|
+
}
|
|
348
|
+
}
|
|
349
|
+
function getGroupContextMode(conversationId, conversation) {
|
|
350
|
+
if (conversation?.type !== 'group')
|
|
351
|
+
return undefined;
|
|
352
|
+
if (pendingMembershipChanges.has(conversationId))
|
|
353
|
+
return 'membership_change';
|
|
354
|
+
if (!promptedGroupContextConversationIds.has(conversationId))
|
|
355
|
+
return 'initial';
|
|
356
|
+
return undefined;
|
|
357
|
+
}
|
|
358
|
+
function markGroupContextModeUsed(conversationId, mode) {
|
|
359
|
+
if (!mode)
|
|
360
|
+
return;
|
|
361
|
+
promptedGroupContextConversationIds.add(conversationId);
|
|
362
|
+
if (mode === 'membership_change') {
|
|
363
|
+
pendingMembershipChanges.delete(conversationId);
|
|
364
|
+
}
|
|
365
|
+
}
|
|
337
366
|
async function loadSenderRuntimeState(conversationId, senderId) {
|
|
338
367
|
try {
|
|
339
368
|
return normalizeRuntimeTurnState(await rtdbRead(`/turn-state/${conversationId}/${senderId}`));
|
|
@@ -353,9 +382,15 @@ export async function main() {
|
|
|
353
382
|
agentId,
|
|
354
383
|
conversation,
|
|
355
384
|
page,
|
|
385
|
+
activeSelfContextId: input.activeSelfContextId,
|
|
386
|
+
selfContexts: input.selfContexts,
|
|
356
387
|
message: input.message,
|
|
357
388
|
senderName: input.senderName,
|
|
358
389
|
isOwner: input.isOwner,
|
|
390
|
+
ownerId,
|
|
391
|
+
ownerName,
|
|
392
|
+
membershipChange: pendingMembershipChanges.get(input.conversationId) ?? null,
|
|
393
|
+
groupContextMode: getGroupContextMode(input.conversationId, conversation),
|
|
359
394
|
});
|
|
360
395
|
}
|
|
361
396
|
function writeState(session) {
|
|
@@ -452,6 +487,32 @@ export async function main() {
|
|
|
452
487
|
client.setTyping(conversationId, false).catch(() => { });
|
|
453
488
|
sessions.delete(conversationId);
|
|
454
489
|
}
|
|
490
|
+
async function resetRuntimeSession(session) {
|
|
491
|
+
const conversationId = session.conversationId;
|
|
492
|
+
session.resetRequested = true;
|
|
493
|
+
const droppedPrompts = session.queue.splice(0);
|
|
494
|
+
await markQueuedPromptsRejected(conversationId, droppedPrompts);
|
|
495
|
+
clearStoredThreadId(runtimeId, agentId, conversationId, session.environment.baseCwd, session.environment.mode);
|
|
496
|
+
session.adapter.clearThreadId();
|
|
497
|
+
session.activeSelfContextId = null;
|
|
498
|
+
session.state.lastError = undefined;
|
|
499
|
+
if (session.running) {
|
|
500
|
+
await session.adapter.interrupt();
|
|
501
|
+
session.turnState = 'interrupted';
|
|
502
|
+
}
|
|
503
|
+
else {
|
|
504
|
+
session.turnState = 'idle';
|
|
505
|
+
session.currentTurnId = null;
|
|
506
|
+
session.currentTurnOpenedAt = null;
|
|
507
|
+
session.lastAcceptedIntent = null;
|
|
508
|
+
session.resetRequested = false;
|
|
509
|
+
}
|
|
510
|
+
stopVisibleWorkSignal(session);
|
|
511
|
+
clearStreaming(conversationId);
|
|
512
|
+
client.setTyping(conversationId, false).catch(() => { });
|
|
513
|
+
writeState(session);
|
|
514
|
+
writeTurn(session);
|
|
515
|
+
}
|
|
455
516
|
function evictOldestIdle() {
|
|
456
517
|
let oldest = null;
|
|
457
518
|
for (const session of sessions.values()) {
|
|
@@ -531,6 +592,7 @@ export async function main() {
|
|
|
531
592
|
currentTurnOpenedAt: null,
|
|
532
593
|
activeSelfContextId: null,
|
|
533
594
|
lastAcceptedIntent: null,
|
|
595
|
+
resetRequested: false,
|
|
534
596
|
lastActivity: Date.now(),
|
|
535
597
|
typingKeepaliveTimer: null,
|
|
536
598
|
closed: false,
|
|
@@ -594,14 +656,13 @@ export async function main() {
|
|
|
594
656
|
message: input.message,
|
|
595
657
|
senderName: input.senderName,
|
|
596
658
|
isOwner: input.isOwner,
|
|
659
|
+
activeSelfContextId: input.activeSelfContextId,
|
|
660
|
+
selfContexts: input.selfContexts,
|
|
597
661
|
hydratedPage: input.hydratedPage,
|
|
598
662
|
});
|
|
599
663
|
const behavior = input.behavior ?? hydrated.behavior;
|
|
600
|
-
const
|
|
601
|
-
|
|
602
|
-
: Array.isArray(input.selfContexts)
|
|
603
|
-
? input.selfContexts
|
|
604
|
-
: hydrated.selfContexts;
|
|
664
|
+
const activeSelfContextId = hydrated.activeSelfContextId;
|
|
665
|
+
const selfContexts = hydrated.selfContexts;
|
|
605
666
|
const participantContext = hydrated.participantContext;
|
|
606
667
|
const autoReply = decideAutoReply(participantContext, behavior);
|
|
607
668
|
if (!autoReply.allow) {
|
|
@@ -609,6 +670,7 @@ export async function main() {
|
|
|
609
670
|
return;
|
|
610
671
|
}
|
|
611
672
|
console.error(`[canon-codex] [${input.conversationId.slice(0, 8)}] Message from ${input.senderName}: "${content.slice(0, 80)}" (${autoReply.reason})`);
|
|
673
|
+
markGroupContextModeUsed(input.conversationId, participantContext.groupContextMode);
|
|
612
674
|
let session;
|
|
613
675
|
try {
|
|
614
676
|
session = await getOrCreateSession(input.conversationId);
|
|
@@ -618,7 +680,7 @@ export async function main() {
|
|
|
618
680
|
const userMessage = error instanceof ExecutionEnvironmentError ? error.userMessage : message;
|
|
619
681
|
console.error(`[canon-codex] [${input.conversationId.slice(0, 8)}] Failed to create session: ${message}`);
|
|
620
682
|
await client.sendMessage(input.conversationId, `I couldn't start a coding session for this workspace: ${userMessage}`, {
|
|
621
|
-
...(
|
|
683
|
+
...(activeSelfContextId ? { selfContextId: activeSelfContextId } : {}),
|
|
622
684
|
metadata: {
|
|
623
685
|
turnSemantics: 'turn_complete',
|
|
624
686
|
turnComplete: true,
|
|
@@ -628,7 +690,7 @@ export async function main() {
|
|
|
628
690
|
return;
|
|
629
691
|
}
|
|
630
692
|
const turnMetadata = normalizeTurnMetadata(input.message.metadata);
|
|
631
|
-
session.activeSelfContextId =
|
|
693
|
+
session.activeSelfContextId = activeSelfContextId;
|
|
632
694
|
const deliveryIntent = turnMetadata?.deliveryIntent ?? 'queue';
|
|
633
695
|
const shouldMarkAccepted = turnMetadata?.inboundDisposition === 'queued';
|
|
634
696
|
const prompt = buildCanonPrompt({
|
|
@@ -680,6 +742,9 @@ export async function main() {
|
|
|
680
742
|
const handleCodexEvent = (event) => {
|
|
681
743
|
session.lastActivity = Date.now();
|
|
682
744
|
if (event.type === 'thread.started') {
|
|
745
|
+
if (session.resetRequested) {
|
|
746
|
+
return;
|
|
747
|
+
}
|
|
683
748
|
saveStoredThreadId(runtimeId, agentId, session.conversationId, session.environment.baseCwd, event.threadId, session.environment.mode, session.policyFingerprint);
|
|
684
749
|
console.error(`[canon-codex] [${session.conversationId.slice(0, 8)}] Thread ${event.threadId}`);
|
|
685
750
|
return;
|
|
@@ -727,7 +792,7 @@ export async function main() {
|
|
|
727
792
|
clearStoredThread();
|
|
728
793
|
result = await runTurnOnce();
|
|
729
794
|
}
|
|
730
|
-
if (result.threadId) {
|
|
795
|
+
if (result.threadId && !session.resetRequested) {
|
|
731
796
|
saveStoredThreadId(runtimeId, agentId, session.conversationId, session.environment.baseCwd, result.threadId, session.environment.mode, session.policyFingerprint);
|
|
732
797
|
}
|
|
733
798
|
if (!result.interrupted && result.finalMessage) {
|
|
@@ -811,6 +876,7 @@ export async function main() {
|
|
|
811
876
|
session.currentTurnId = null;
|
|
812
877
|
session.currentTurnOpenedAt = null;
|
|
813
878
|
session.lastAcceptedIntent = null;
|
|
879
|
+
session.resetRequested = false;
|
|
814
880
|
session.lastActivity = Date.now();
|
|
815
881
|
writeState(session);
|
|
816
882
|
writeTurn(session);
|
|
@@ -986,6 +1052,7 @@ export async function main() {
|
|
|
986
1052
|
senderName: message.senderName || message.senderId,
|
|
987
1053
|
isOwner: message.isOwner ?? (ownerId != null && message.senderId === ownerId),
|
|
988
1054
|
behavior: payload.behavior,
|
|
1055
|
+
activeSelfContextId: payload.activeSelfContextId,
|
|
989
1056
|
selfContexts: payload.selfContexts,
|
|
990
1057
|
});
|
|
991
1058
|
if (message.id) {
|
|
@@ -996,6 +1063,9 @@ export async function main() {
|
|
|
996
1063
|
});
|
|
997
1064
|
}
|
|
998
1065
|
},
|
|
1066
|
+
onConversationUpdated: (payload) => {
|
|
1067
|
+
handleConversationUpdated(payload);
|
|
1068
|
+
},
|
|
999
1069
|
onConnected: () => {
|
|
1000
1070
|
streamConnected = true;
|
|
1001
1071
|
void publishRuntimeHeartbeat();
|
|
@@ -1090,7 +1160,7 @@ export async function main() {
|
|
|
1090
1160
|
await enqueueInboundMessage({
|
|
1091
1161
|
conversationId: conversation.id,
|
|
1092
1162
|
message: latestMessage,
|
|
1093
|
-
senderName: latestMessage.senderId,
|
|
1163
|
+
senderName: latestMessage.senderName || latestMessage.senderId,
|
|
1094
1164
|
isOwner: ownerId != null && latestMessage.senderId === ownerId,
|
|
1095
1165
|
behavior: latestPage.behavior,
|
|
1096
1166
|
selfContexts: latestPage.selfContexts,
|
|
@@ -1153,7 +1223,7 @@ export async function main() {
|
|
|
1153
1223
|
continue;
|
|
1154
1224
|
const signal = raw;
|
|
1155
1225
|
const timestamp = signal.updatedAt ?? 0;
|
|
1156
|
-
if ((signal.type !== 'interrupt' && signal.type !== 'stop_and_drop')
|
|
1226
|
+
if ((signal.type !== 'interrupt' && signal.type !== 'stop_and_drop' && signal.type !== 'new_session')
|
|
1157
1227
|
|| timestamp <= (lastSeenSignal.get(conversationId) ?? 0)) {
|
|
1158
1228
|
continue;
|
|
1159
1229
|
}
|
|
@@ -1161,6 +1231,12 @@ export async function main() {
|
|
|
1161
1231
|
const session = sessions.get(conversationId);
|
|
1162
1232
|
if (!session || session.closed)
|
|
1163
1233
|
continue;
|
|
1234
|
+
if (signal.type === 'new_session') {
|
|
1235
|
+
console.error(`[canon-codex] [${conversationId.slice(0, 8)}] new_session signal`);
|
|
1236
|
+
await resetRuntimeSession(session);
|
|
1237
|
+
await rtdbWrite(`/control/${conversationId}/${agentId}/signal`, null).catch(() => { });
|
|
1238
|
+
continue;
|
|
1239
|
+
}
|
|
1164
1240
|
if (!session.running && (signal.type !== 'stop_and_drop' || session.queue.length === 0)) {
|
|
1165
1241
|
await rtdbWrite(`/control/${conversationId}/${agentId}/signal`, null).catch(() => { });
|
|
1166
1242
|
continue;
|
package/dist/inbound-policy.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { type CanonConversation, type ResolvedAgentBehaviorPolicy } from '@canonmsg/core';
|
|
1
|
+
import { type CanonGroupContext, type CanonGroupContextMode, type CanonConversation, type ResolvedAgentBehaviorPolicy } from '@canonmsg/core';
|
|
2
2
|
export interface InboundParticipantContext {
|
|
3
3
|
conversationType: CanonConversation['type'] | 'unknown';
|
|
4
4
|
memberCount: number | null;
|
|
@@ -6,6 +6,8 @@ export interface InboundParticipantContext {
|
|
|
6
6
|
senderName: string;
|
|
7
7
|
isOwner: boolean;
|
|
8
8
|
mentionedAgent: boolean;
|
|
9
|
+
groupContext?: CanonGroupContext;
|
|
10
|
+
groupContextMode?: CanonGroupContextMode;
|
|
9
11
|
recentSenderTypes: Array<'human' | 'ai_agent'>;
|
|
10
12
|
recentHumanCount: number;
|
|
11
13
|
recentAgentCount: number;
|
package/dist/inbound-policy.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { evaluateParticipationPolicy, resolveAgentBehaviorPolicy, } from '@canonmsg/core';
|
|
1
|
+
import { buildCompactGroupContextLines, evaluateParticipationPolicy, resolveAgentBehaviorPolicy, } from '@canonmsg/core';
|
|
2
2
|
function formatRecentSenders(senderTypes) {
|
|
3
3
|
if (senderTypes.length === 0)
|
|
4
4
|
return 'none';
|
|
@@ -18,6 +18,9 @@ export function buildInboundContextLines(context) {
|
|
|
18
18
|
`Latest sender name: ${context.senderName}`,
|
|
19
19
|
`Latest sender type: ${context.senderType}`,
|
|
20
20
|
`Conversation type: ${conversationTypeLabel}`,
|
|
21
|
+
...(context.groupContext && context.groupContextMode
|
|
22
|
+
? buildCompactGroupContextLines(context.groupContext, context.groupContextMode)
|
|
23
|
+
: []),
|
|
21
24
|
`Directly addressed to this agent: ${context.mentionedAgent ? 'yes' : 'no'}`,
|
|
22
25
|
`Recent sender pattern: ${formatRecentSenders(context.recentSenderTypes)}`,
|
|
23
26
|
`Recent human messages: ${context.recentHumanCount}`,
|
package/dist/register.js
CHANGED
|
@@ -97,7 +97,9 @@ export async function main() {
|
|
|
97
97
|
console.log(`Approved! Agent: ${result.agentName} (${result.agentId})`);
|
|
98
98
|
console.log(`Saved as profile "${profileName}" in ~/.canon/agents.json`);
|
|
99
99
|
console.log('Start it with: CANON_AGENT=' + profileName + ' canon-codex --cwd /path/to/project');
|
|
100
|
-
console.log('Keep that terminal open
|
|
100
|
+
console.log('Keep that terminal open while you want Canon to reach the agent.');
|
|
101
|
+
console.log('Closing it, logging out, rebooting, or sleeping long enough to stop the process takes this local agent offline until you revive it.');
|
|
102
|
+
console.log('Docs: https://canonmail.com/agents/integrations');
|
|
101
103
|
break;
|
|
102
104
|
}
|
|
103
105
|
case 'rejected':
|
package/dist/session-store.js
CHANGED
|
@@ -84,11 +84,26 @@ export function saveStoredThreadId(runtimeId, agentId, conversationId, baseCwd,
|
|
|
84
84
|
}
|
|
85
85
|
export function clearStoredThreadId(runtimeId, agentId, conversationId, baseCwd, executionMode) {
|
|
86
86
|
if (runtimeId) {
|
|
87
|
+
const existing = baseCwd
|
|
88
|
+
? loadRuntimeSessionState(runtimeId, {
|
|
89
|
+
conversationId,
|
|
90
|
+
baseCwd,
|
|
91
|
+
executionMode,
|
|
92
|
+
})
|
|
93
|
+
: null;
|
|
87
94
|
clearRuntimeSessionState(runtimeId, {
|
|
88
95
|
conversationId,
|
|
89
96
|
baseCwd,
|
|
90
97
|
executionMode,
|
|
91
98
|
});
|
|
99
|
+
if (existing?.lastInboundMessageId && baseCwd) {
|
|
100
|
+
saveRuntimeSessionState(runtimeId, {
|
|
101
|
+
conversationId,
|
|
102
|
+
baseCwd,
|
|
103
|
+
...(executionMode ? { executionMode } : {}),
|
|
104
|
+
lastInboundMessageId: existing.lastInboundMessageId,
|
|
105
|
+
});
|
|
106
|
+
}
|
|
92
107
|
return;
|
|
93
108
|
}
|
|
94
109
|
const store = loadStore();
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@canonmsg/codex-plugin",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.11.0",
|
|
4
4
|
"description": "Canon host integration for Codex CLI",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/index.js",
|
|
@@ -29,8 +29,8 @@
|
|
|
29
29
|
"prepack": "npm run build"
|
|
30
30
|
},
|
|
31
31
|
"dependencies": {
|
|
32
|
-
"@canonmsg/agent-sdk": "^1.
|
|
33
|
-
"@canonmsg/core": "^0.
|
|
32
|
+
"@canonmsg/agent-sdk": "^1.3.0",
|
|
33
|
+
"@canonmsg/core": "^0.17.0"
|
|
34
34
|
},
|
|
35
35
|
"engines": {
|
|
36
36
|
"node": ">=18.0.0"
|