@canonmsg/codex-plugin 0.6.4 โ 0.6.6
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/dist/host-runtime.d.ts +21 -0
- package/dist/host-runtime.js +80 -2
- package/dist/host.js +48 -1
- package/package.json +6 -5
package/dist/host-runtime.d.ts
CHANGED
|
@@ -37,6 +37,12 @@ interface HostWorkspaceResolverOption {
|
|
|
37
37
|
id: string;
|
|
38
38
|
cwd: string;
|
|
39
39
|
}
|
|
40
|
+
export declare const HOST_ADMISSION_ACTION_CAPABILITIES: Readonly<{
|
|
41
|
+
canStartDirectConversation: false;
|
|
42
|
+
canSendContactRequest: false;
|
|
43
|
+
canApprovePendingContactRequests: false;
|
|
44
|
+
canRejectPendingContactRequests: false;
|
|
45
|
+
}>;
|
|
40
46
|
export declare function buildCanonHostPrompt(input: {
|
|
41
47
|
hostLabel: string;
|
|
42
48
|
content: string;
|
|
@@ -81,6 +87,21 @@ export declare function buildHydratedInboundContext(input: {
|
|
|
81
87
|
hydratedFromPage: boolean;
|
|
82
88
|
};
|
|
83
89
|
export declare function publishHostAgentRuntime(agentId: string, clientType: AgentClientType, runtime: AgentRuntime): Promise<void>;
|
|
90
|
+
export declare function publishHostSessionSnapshots(input: {
|
|
91
|
+
conversationIds: string[];
|
|
92
|
+
agentId: string;
|
|
93
|
+
clientType: AgentClientType;
|
|
94
|
+
runtime: AgentRuntime;
|
|
95
|
+
workspaceOptions: HostWorkspaceResolverOption[];
|
|
96
|
+
defaultCwd: string;
|
|
97
|
+
liveSessionConfigByConversation?: ReadonlyMap<string, {
|
|
98
|
+
model?: string;
|
|
99
|
+
permissionMode?: string;
|
|
100
|
+
workspaceId?: string;
|
|
101
|
+
executionMode?: SessionWorkspaceConfig['executionMode'];
|
|
102
|
+
executionBranch?: string | null;
|
|
103
|
+
}>;
|
|
104
|
+
}): Promise<void>;
|
|
84
105
|
export declare function readHostSessionConfig<TExtra extends string = never>(raw: unknown, extraStringFields?: readonly TExtra[]): (SessionWorkspaceConfig & Partial<Record<TExtra, string>>) | null;
|
|
85
106
|
export declare function loadHostSessionConfig<TExtra extends string = never>(input: {
|
|
86
107
|
conversationId: string;
|
package/dist/host-runtime.js
CHANGED
|
@@ -11,7 +11,13 @@
|
|
|
11
11
|
* behavior here, update that copy too and adjust the shared golden
|
|
12
12
|
* fixture test (`packages/codex-plugin/src/host-runtime.test.ts`).
|
|
13
13
|
*/
|
|
14
|
-
import { buildBehaviorPolicyLines, buildParticipationHistorySnapshot, buildWorkSessionsPromptLines, mergeWorkSessionContexts, normalizeOptionalString, readSessionWorkspaceConfig, resolveConfiguredWorkspaceCwd, rtdbRead, rtdbWrite, } from '@canonmsg/core';
|
|
14
|
+
import { buildAgentSessionSnapshot, buildConversationWorktreeSpec, buildBehaviorPolicyLines, buildParticipationHistorySnapshot, buildWorkSessionsPromptLines, mergeWorkSessionContexts, normalizeOptionalString, patchAgentSessionSnapshot, readSessionWorkspaceConfig, resolveConfiguredWorkspaceCwd, rtdbRead, rtdbWrite, } from '@canonmsg/core';
|
|
15
|
+
export const HOST_ADMISSION_ACTION_CAPABILITIES = Object.freeze({
|
|
16
|
+
canStartDirectConversation: false,
|
|
17
|
+
canSendContactRequest: false,
|
|
18
|
+
canApprovePendingContactRequests: false,
|
|
19
|
+
canRejectPendingContactRequests: false,
|
|
20
|
+
});
|
|
15
21
|
export function buildCanonHostPrompt(input) {
|
|
16
22
|
const resolvedWorkSessions = mergeWorkSessionContexts(input.workSession, input.workSessions);
|
|
17
23
|
return [
|
|
@@ -69,7 +75,21 @@ function describeContactCard(card) {
|
|
|
69
75
|
if (card.about)
|
|
70
76
|
parts.push(`about: ${card.about}`);
|
|
71
77
|
const identity = `๐ Contact card: "${card.displayName}" (${parts.join(' ยท ')}).`;
|
|
72
|
-
const
|
|
78
|
+
const missingCapabilities = [
|
|
79
|
+
!HOST_ADMISSION_ACTION_CAPABILITIES.canStartDirectConversation
|
|
80
|
+
? 'start a direct conversation'
|
|
81
|
+
: null,
|
|
82
|
+
!HOST_ADMISSION_ACTION_CAPABILITIES.canSendContactRequest
|
|
83
|
+
? 'send a contact request'
|
|
84
|
+
: null,
|
|
85
|
+
!HOST_ADMISSION_ACTION_CAPABILITIES.canApprovePendingContactRequests
|
|
86
|
+
? 'approve pending requests'
|
|
87
|
+
: null,
|
|
88
|
+
!HOST_ADMISSION_ACTION_CAPABILITIES.canRejectPendingContactRequests
|
|
89
|
+
? 'reject pending requests'
|
|
90
|
+
: null,
|
|
91
|
+
].filter(Boolean).join(', ');
|
|
92
|
+
const hint = `This host can inspect the card, but Canon admission actions are missing here. Missing capabilities: ${missingCapabilities}. Use another Canon surface for userId ${card.userId}.`;
|
|
73
93
|
return `${identity}\n${hint}`;
|
|
74
94
|
}
|
|
75
95
|
function describeAttachment(attachment, materialized) {
|
|
@@ -116,6 +136,64 @@ export async function publishHostAgentRuntime(agentId, clientType, runtime) {
|
|
|
116
136
|
updatedAt: { '.sv': 'timestamp' },
|
|
117
137
|
});
|
|
118
138
|
}
|
|
139
|
+
export async function publishHostSessionSnapshots(input) {
|
|
140
|
+
if (input.conversationIds.length === 0) {
|
|
141
|
+
return;
|
|
142
|
+
}
|
|
143
|
+
await Promise.all(input.conversationIds.map(async (conversationId) => {
|
|
144
|
+
const persistedConfig = await loadHostSessionConfig({
|
|
145
|
+
conversationId,
|
|
146
|
+
agentId: input.agentId,
|
|
147
|
+
extraStringFields: ['permissionMode'],
|
|
148
|
+
});
|
|
149
|
+
const liveConfig = input.liveSessionConfigByConversation?.get(conversationId) ?? null;
|
|
150
|
+
const mergedConfig = {
|
|
151
|
+
...(persistedConfig ?? {}),
|
|
152
|
+
...(liveConfig ?? {}),
|
|
153
|
+
};
|
|
154
|
+
const snapshot = buildAgentSessionSnapshot({
|
|
155
|
+
conversationId,
|
|
156
|
+
agentId: input.agentId,
|
|
157
|
+
runtime: {
|
|
158
|
+
...input.runtime,
|
|
159
|
+
clientType: input.clientType,
|
|
160
|
+
hostMode: true,
|
|
161
|
+
},
|
|
162
|
+
sessionConfig: {
|
|
163
|
+
...(mergedConfig.model ? { model: mergedConfig.model } : {}),
|
|
164
|
+
...(mergedConfig.permissionMode ? { permissionMode: mergedConfig.permissionMode } : {}),
|
|
165
|
+
...(mergedConfig.workspaceId ? { workspaceId: mergedConfig.workspaceId } : {}),
|
|
166
|
+
...(mergedConfig.executionMode ? { executionMode: mergedConfig.executionMode } : {}),
|
|
167
|
+
},
|
|
168
|
+
lastHeartbeatAt: undefined,
|
|
169
|
+
});
|
|
170
|
+
let executionBranch = liveConfig?.executionBranch ?? null;
|
|
171
|
+
if (!executionBranch && snapshot.executionMode === 'worktree' && snapshot.workspaceId) {
|
|
172
|
+
const workspace = input.workspaceOptions.find((option) => option.id === snapshot.workspaceId);
|
|
173
|
+
if (workspace) {
|
|
174
|
+
executionBranch = buildConversationWorktreeSpec({
|
|
175
|
+
agentId: input.agentId,
|
|
176
|
+
conversationId,
|
|
177
|
+
workspaceCwd: workspace.cwd,
|
|
178
|
+
}).branch;
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
return patchAgentSessionSnapshot(conversationId, input.agentId, {
|
|
182
|
+
clientType: input.clientType,
|
|
183
|
+
hostMode: true,
|
|
184
|
+
model: snapshot.model ?? null,
|
|
185
|
+
permissionMode: snapshot.permissionMode ?? null,
|
|
186
|
+
workspaceId: snapshot.workspaceId ?? null,
|
|
187
|
+
executionMode: snapshot.executionMode ?? null,
|
|
188
|
+
executionBranch,
|
|
189
|
+
modelOptions: snapshot.modelOptions,
|
|
190
|
+
permissionModeOptions: snapshot.permissionModeOptions,
|
|
191
|
+
workspaceOptions: snapshot.workspaceOptions,
|
|
192
|
+
availableExecutionModes: snapshot.availableExecutionModes,
|
|
193
|
+
lastHeartbeatAt: { '.sv': 'timestamp' },
|
|
194
|
+
});
|
|
195
|
+
}));
|
|
196
|
+
}
|
|
119
197
|
export function readHostSessionConfig(raw, extraStringFields = []) {
|
|
120
198
|
const baseConfig = readSessionWorkspaceConfig(raw);
|
|
121
199
|
if (!raw || typeof raw !== 'object') {
|
package/dist/host.js
CHANGED
|
@@ -4,7 +4,7 @@ import { randomUUID } from 'node:crypto';
|
|
|
4
4
|
import { parseArgs } from 'node:util';
|
|
5
5
|
import { getCodexImagePath, materializeMessageMedia, } from '@canonmsg/agent-sdk';
|
|
6
6
|
import { buildConfiguredWorkspaceOptions, buildPublicWorkspaceOptions, EXECUTION_ENVIRONMENT_MODES, ExecutionEnvironmentError, CanonClient, CanonStream, clearSessionState, clearTurnState, DEFAULT_PARTICIPATION_HISTORY_FETCH_LIMIT, DEFAULT_RUNTIME_CAPABILITIES, FINAL_MESSAGE_HANDOFF_MS, getActiveProfile, initRTDBAuth, normalizeTurnMetadata, normalizeTurnState, prepareConversationEnvironment, releaseLock, releaseConversationEnvironment, resolveCanonAgent, rtdbRead, rtdbWrite, shouldTriggerAgentTurn, writeSessionState, writeTurnState, } from '@canonmsg/core';
|
|
7
|
-
import { buildCanonHostPrompt, buildHydratedInboundContext, createConversationMetadataLoader, loadHostSessionConfig, publishHostAgentRuntime, renderCanonHostInboundContent, resolveHostWorkspaceCwd, } from './host-runtime.js';
|
|
7
|
+
import { buildCanonHostPrompt, buildHydratedInboundContext, createConversationMetadataLoader, loadHostSessionConfig, publishHostAgentRuntime, publishHostSessionSnapshots, renderCanonHostInboundContent, resolveHostWorkspaceCwd, } from './host-runtime.js';
|
|
8
8
|
import { buildInboundContextLines, decideAutoReply, } from './inbound-policy.js';
|
|
9
9
|
import { CodexConversationAdapter, } from './adapter.js';
|
|
10
10
|
import { clearStoredThreadId, loadStoredThreadId, saveStoredThreadId, } from './session-store.js';
|
|
@@ -138,10 +138,27 @@ export async function main() {
|
|
|
138
138
|
const sessions = new Map();
|
|
139
139
|
const pendingSessionCreations = new Map();
|
|
140
140
|
const conversationCache = new Map();
|
|
141
|
+
const knownConversationIds = new Set();
|
|
142
|
+
let lastKnownConversationRefreshAt = 0;
|
|
141
143
|
const { getConversationMeta } = createConversationMetadataLoader({
|
|
142
144
|
client,
|
|
143
145
|
conversationCache,
|
|
144
146
|
});
|
|
147
|
+
function resolveWorkspaceIdForBaseCwd(baseCwd) {
|
|
148
|
+
return workspaceOptions.find((option) => option.cwd === baseCwd)?.id;
|
|
149
|
+
}
|
|
150
|
+
async function refreshKnownConversationIds(force = false) {
|
|
151
|
+
if (!force && Date.now() - lastKnownConversationRefreshAt < HEARTBEAT_MS) {
|
|
152
|
+
return;
|
|
153
|
+
}
|
|
154
|
+
const conversations = await client.getConversations();
|
|
155
|
+
knownConversationIds.clear();
|
|
156
|
+
for (const conversation of conversations) {
|
|
157
|
+
knownConversationIds.add(conversation.id);
|
|
158
|
+
conversationCache.set(conversation.id, conversation);
|
|
159
|
+
}
|
|
160
|
+
lastKnownConversationRefreshAt = Date.now();
|
|
161
|
+
}
|
|
145
162
|
async function loadSenderRuntimeState(conversationId, senderId) {
|
|
146
163
|
try {
|
|
147
164
|
const [turnState, sessionState] = await Promise.all([
|
|
@@ -236,6 +253,7 @@ export async function main() {
|
|
|
236
253
|
}
|
|
237
254
|
}
|
|
238
255
|
async function getOrCreateSession(conversationId) {
|
|
256
|
+
knownConversationIds.add(conversationId);
|
|
239
257
|
const existing = sessions.get(conversationId);
|
|
240
258
|
if (existing && !existing.closed) {
|
|
241
259
|
existing.lastActivity = Date.now();
|
|
@@ -335,6 +353,7 @@ export async function main() {
|
|
|
335
353
|
void runNextTurn(session);
|
|
336
354
|
}
|
|
337
355
|
async function enqueueInboundMessage(input) {
|
|
356
|
+
knownConversationIds.add(input.conversationId);
|
|
338
357
|
let materialized = [];
|
|
339
358
|
if (input.message.id) {
|
|
340
359
|
try {
|
|
@@ -555,9 +574,34 @@ export async function main() {
|
|
|
555
574
|
const publishRuntimeHeartbeat = async () => {
|
|
556
575
|
if (!streamConnected)
|
|
557
576
|
return;
|
|
577
|
+
await refreshKnownConversationIds().catch((error) => {
|
|
578
|
+
console.error('[canon-codex] Failed to refresh known conversations:', error);
|
|
579
|
+
});
|
|
558
580
|
await publishAgentRuntime(agentId, runtimeDescriptor).catch((error) => {
|
|
559
581
|
console.error('[canon-codex] Failed to publish agent runtime:', error);
|
|
560
582
|
});
|
|
583
|
+
await publishHostSessionSnapshots({
|
|
584
|
+
conversationIds: Array.from(knownConversationIds),
|
|
585
|
+
agentId,
|
|
586
|
+
clientType: 'codex',
|
|
587
|
+
runtime: runtimeDescriptor,
|
|
588
|
+
workspaceOptions,
|
|
589
|
+
defaultCwd: workingDir,
|
|
590
|
+
liveSessionConfigByConversation: new Map(Array.from(sessions.values()).map((session) => {
|
|
591
|
+
const workspaceId = resolveWorkspaceIdForBaseCwd(session.environment.baseCwd);
|
|
592
|
+
return [
|
|
593
|
+
session.conversationId,
|
|
594
|
+
{
|
|
595
|
+
...(session.state.model ? { model: session.state.model } : {}),
|
|
596
|
+
...(workspaceId ? { workspaceId } : {}),
|
|
597
|
+
executionMode: session.environment.mode,
|
|
598
|
+
executionBranch: session.environment.branch ?? null,
|
|
599
|
+
},
|
|
600
|
+
];
|
|
601
|
+
})),
|
|
602
|
+
}).catch((error) => {
|
|
603
|
+
console.error('[canon-codex] Failed to publish session snapshots:', error);
|
|
604
|
+
});
|
|
561
605
|
};
|
|
562
606
|
const stream = new CanonStream({
|
|
563
607
|
apiKey,
|
|
@@ -614,7 +658,10 @@ export async function main() {
|
|
|
614
658
|
}
|
|
615
659
|
try {
|
|
616
660
|
const conversations = await client.getConversations();
|
|
661
|
+
lastKnownConversationRefreshAt = Date.now();
|
|
617
662
|
for (const conversation of conversations) {
|
|
663
|
+
knownConversationIds.add(conversation.id);
|
|
664
|
+
conversationCache.set(conversation.id, conversation);
|
|
618
665
|
clearStreaming(conversation.id);
|
|
619
666
|
clearSessionState(conversation.id, agentId).catch(() => { });
|
|
620
667
|
clearTurnState(conversation.id, agentId).catch(() => { });
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@canonmsg/codex-plugin",
|
|
3
|
-
"version": "0.6.
|
|
3
|
+
"version": "0.6.6",
|
|
4
4
|
"description": "Canon host integration for Codex CLI",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/index.js",
|
|
@@ -21,15 +21,16 @@
|
|
|
21
21
|
"scripts"
|
|
22
22
|
],
|
|
23
23
|
"scripts": {
|
|
24
|
-
"
|
|
25
|
-
"
|
|
24
|
+
"prepare:workspace-deps": "npm --prefix ../core run build && npm --prefix ../agent-sdk run build",
|
|
25
|
+
"build": "npm run prepare:workspace-deps && node -e \"require('fs').rmSync('dist',{recursive:true,force:true})\" && tsc",
|
|
26
|
+
"dev": "npm run prepare:workspace-deps && tsc --watch",
|
|
26
27
|
"smoke": "node scripts/smoke-test.mjs",
|
|
27
28
|
"test": "vitest run",
|
|
28
29
|
"prepack": "npm run build"
|
|
29
30
|
},
|
|
30
31
|
"dependencies": {
|
|
31
|
-
"@canonmsg/agent-sdk": "^0.8.
|
|
32
|
-
"@canonmsg/core": "^0.7.
|
|
32
|
+
"@canonmsg/agent-sdk": "^0.8.3",
|
|
33
|
+
"@canonmsg/core": "^0.7.5"
|
|
33
34
|
},
|
|
34
35
|
"engines": {
|
|
35
36
|
"node": ">=18.0.0"
|