@canonmsg/codex-plugin 0.6.5 โ 0.7.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 +11 -1
- package/dist/host-runtime.d.ts +29 -2
- package/dist/host-runtime.js +93 -2
- package/dist/host.js +177 -2
- package/package.json +3 -3
package/README.md
CHANGED
|
@@ -37,7 +37,7 @@ You do not need a git repo for host mode. The plugin passes `--skip-git-repo-che
|
|
|
37
37
|
|
|
38
38
|
## Current limitation
|
|
39
39
|
|
|
40
|
-
The stable `codex exec --json` surface exposes
|
|
40
|
+
The stable `codex exec --json` surface exposes thinking state, tool activity, and completed assistant-message previews, but not token-by-token text deltas. v1 therefore publishes live progress and message snapshots without claiming true token streaming.
|
|
41
41
|
|
|
42
42
|
## Working directory
|
|
43
43
|
|
|
@@ -45,12 +45,22 @@ The stable `codex exec --json` surface exposes completed assistant messages and
|
|
|
45
45
|
canon-codex --cwd /path/to/project
|
|
46
46
|
```
|
|
47
47
|
|
|
48
|
+
Advertise multiple project choices to the Canon app:
|
|
49
|
+
|
|
50
|
+
```bash
|
|
51
|
+
canon-codex --cwd ~/dev --workspace ~/dev/canon --workspace ~/dev/yumyumv2
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
`--cwd` is the default workspace. Each `--workspace` value appears as a selectable workspace during session creation. Worktree mode creates a per-conversation git worktree under `~/.canon/conversation-worktrees`; shared-workspace mode runs directly in the selected directory.
|
|
55
|
+
|
|
48
56
|
Useful flags:
|
|
49
57
|
|
|
50
58
|
```bash
|
|
51
59
|
canon-codex --cwd /path/to/project --model gpt-5.4 --full-auto
|
|
52
60
|
```
|
|
53
61
|
|
|
62
|
+
Codex also supports `--add-dir /extra/path` for additional writable directories passed through to `codex exec`. Canon does not yet render those extra directories as workspace choices.
|
|
63
|
+
|
|
54
64
|
Recent Codex CLI releases no longer accept `--ask-for-approval` with `codex exec`. If you previously launched Canon with `--sandbox workspace-write --ask-for-approval never`, switch to `--full-auto`.
|
|
55
65
|
|
|
56
66
|
Local smoke test:
|
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,12 +87,33 @@ 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>;
|
|
84
|
-
export declare function
|
|
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
|
+
effort?: string;
|
|
101
|
+
runtimeControlValues?: Record<string, string>;
|
|
102
|
+
workspaceId?: string;
|
|
103
|
+
executionMode?: SessionWorkspaceConfig['executionMode'];
|
|
104
|
+
executionBranch?: string | null;
|
|
105
|
+
}>;
|
|
106
|
+
}): Promise<void>;
|
|
107
|
+
export declare function readHostSessionConfig<TExtra extends string = never>(raw: unknown, extraStringFields?: readonly TExtra[]): (SessionWorkspaceConfig & Partial<Record<TExtra, string>> & {
|
|
108
|
+
runtimeControlValues?: Record<string, string>;
|
|
109
|
+
}) | null;
|
|
85
110
|
export declare function loadHostSessionConfig<TExtra extends string = never>(input: {
|
|
86
111
|
conversationId: string;
|
|
87
112
|
agentId: string;
|
|
88
113
|
extraStringFields?: readonly TExtra[];
|
|
89
|
-
}): Promise<(SessionWorkspaceConfig & Partial<Record<TExtra, string>>
|
|
114
|
+
}): Promise<(SessionWorkspaceConfig & Partial<Record<TExtra, string>> & {
|
|
115
|
+
runtimeControlValues?: Record<string, string>;
|
|
116
|
+
}) | null>;
|
|
90
117
|
export declare function resolveHostWorkspaceCwd(input: {
|
|
91
118
|
workspaceOptions: HostWorkspaceResolverOption[];
|
|
92
119
|
config: {
|
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,70 @@ 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.effort ? { effort: mergedConfig.effort } : {}),
|
|
166
|
+
...(mergedConfig.runtimeControlValues
|
|
167
|
+
? { runtimeControlValues: mergedConfig.runtimeControlValues }
|
|
168
|
+
: {}),
|
|
169
|
+
...(mergedConfig.workspaceId ? { workspaceId: mergedConfig.workspaceId } : {}),
|
|
170
|
+
...(mergedConfig.executionMode ? { executionMode: mergedConfig.executionMode } : {}),
|
|
171
|
+
},
|
|
172
|
+
lastHeartbeatAt: undefined,
|
|
173
|
+
});
|
|
174
|
+
let executionBranch = liveConfig?.executionBranch ?? null;
|
|
175
|
+
if (!executionBranch && snapshot.executionMode === 'worktree' && snapshot.workspaceId) {
|
|
176
|
+
const workspace = input.workspaceOptions.find((option) => option.id === snapshot.workspaceId);
|
|
177
|
+
if (workspace) {
|
|
178
|
+
executionBranch = buildConversationWorktreeSpec({
|
|
179
|
+
agentId: input.agentId,
|
|
180
|
+
conversationId,
|
|
181
|
+
workspaceCwd: workspace.cwd,
|
|
182
|
+
}).branch;
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
return patchAgentSessionSnapshot(conversationId, input.agentId, {
|
|
186
|
+
clientType: input.clientType,
|
|
187
|
+
hostMode: true,
|
|
188
|
+
model: snapshot.model ?? null,
|
|
189
|
+
permissionMode: snapshot.permissionMode ?? null,
|
|
190
|
+
effort: snapshot.effort ?? null,
|
|
191
|
+
runtimeControlValues: snapshot.runtimeControlValues ?? null,
|
|
192
|
+
workspaceId: snapshot.workspaceId ?? null,
|
|
193
|
+
executionMode: snapshot.executionMode ?? null,
|
|
194
|
+
executionBranch,
|
|
195
|
+
modelOptions: snapshot.modelOptions,
|
|
196
|
+
permissionModeOptions: snapshot.permissionModeOptions,
|
|
197
|
+
workspaceOptions: snapshot.workspaceOptions,
|
|
198
|
+
availableExecutionModes: snapshot.availableExecutionModes,
|
|
199
|
+
lastHeartbeatAt: { '.sv': 'timestamp' },
|
|
200
|
+
});
|
|
201
|
+
}));
|
|
202
|
+
}
|
|
119
203
|
export function readHostSessionConfig(raw, extraStringFields = []) {
|
|
120
204
|
const baseConfig = readSessionWorkspaceConfig(raw);
|
|
121
205
|
if (!raw || typeof raw !== 'object') {
|
|
@@ -126,9 +210,16 @@ export function readHostSessionConfig(raw, extraStringFields = []) {
|
|
|
126
210
|
const value = normalizeOptionalString(data[field]);
|
|
127
211
|
return value ? [[field, value]] : [];
|
|
128
212
|
}));
|
|
213
|
+
const runtimeControlValues = Object.fromEntries(Object.entries(data.runtimeControlValues && typeof data.runtimeControlValues === 'object'
|
|
214
|
+
? data.runtimeControlValues
|
|
215
|
+
: {}).flatMap(([key, value]) => {
|
|
216
|
+
const normalizedValue = normalizeOptionalString(value);
|
|
217
|
+
return normalizedValue ? [[key, normalizedValue]] : [];
|
|
218
|
+
}));
|
|
129
219
|
return {
|
|
130
220
|
...(baseConfig ?? {}),
|
|
131
221
|
...extraConfig,
|
|
222
|
+
...(Object.keys(runtimeControlValues).length > 0 ? { runtimeControlValues } : {}),
|
|
132
223
|
};
|
|
133
224
|
}
|
|
134
225
|
export async function loadHostSessionConfig(input) {
|
package/dist/host.js
CHANGED
|
@@ -3,8 +3,8 @@ import { setDefaultResultOrder } from 'node:dns';
|
|
|
3
3
|
import { randomUUID } from 'node:crypto';
|
|
4
4
|
import { parseArgs } from 'node:util';
|
|
5
5
|
import { getCodexImagePath, materializeMessageMedia, } from '@canonmsg/agent-sdk';
|
|
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';
|
|
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, writeRuntimeInfo, shouldTriggerAgentTurn, writeSessionState, writeTurnState, } from '@canonmsg/core';
|
|
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';
|
|
@@ -23,6 +23,61 @@ const CODEX_RUNTIME_CAPABILITIES = {
|
|
|
23
23
|
};
|
|
24
24
|
let workingDir = process.cwd();
|
|
25
25
|
let workspaceOptions = [];
|
|
26
|
+
function buildCodexRuntimeDescriptor(input) {
|
|
27
|
+
return {
|
|
28
|
+
coreControls: [
|
|
29
|
+
{
|
|
30
|
+
id: 'model',
|
|
31
|
+
label: 'Model',
|
|
32
|
+
options: input.models,
|
|
33
|
+
defaultValue: input.models[0]?.value ?? null,
|
|
34
|
+
availability: 'setup_and_live',
|
|
35
|
+
liveBehavior: 'next_turn',
|
|
36
|
+
selectionPolicy: 'inherit',
|
|
37
|
+
},
|
|
38
|
+
{
|
|
39
|
+
id: 'workspace',
|
|
40
|
+
label: 'Workspace',
|
|
41
|
+
options: input.workspaces.map((workspace) => ({
|
|
42
|
+
value: workspace.id,
|
|
43
|
+
label: workspace.label,
|
|
44
|
+
})),
|
|
45
|
+
defaultValue: input.workspaces[0]?.id ?? null,
|
|
46
|
+
availability: 'setup',
|
|
47
|
+
liveBehavior: 'none',
|
|
48
|
+
selectionPolicy: 'inherit',
|
|
49
|
+
},
|
|
50
|
+
{
|
|
51
|
+
id: 'executionMode',
|
|
52
|
+
label: 'Execution mode',
|
|
53
|
+
options: input.executionModes.map((mode) => ({
|
|
54
|
+
value: mode,
|
|
55
|
+
label: mode === 'worktree' ? 'Isolated worktree' : 'Use shared workspace',
|
|
56
|
+
description: mode === 'worktree'
|
|
57
|
+
? 'Creates or reuses a per-conversation git worktree under ~/.canon/conversation-worktrees when the selected workspace is a git repo.'
|
|
58
|
+
: 'Runs directly in the selected workspace. Canon records usage, but does not create a separate checkout.',
|
|
59
|
+
})),
|
|
60
|
+
defaultValue: null,
|
|
61
|
+
availability: 'setup',
|
|
62
|
+
liveBehavior: 'none',
|
|
63
|
+
selectionPolicy: 'required_explicit',
|
|
64
|
+
},
|
|
65
|
+
],
|
|
66
|
+
runtimeControls: [
|
|
67
|
+
{
|
|
68
|
+
id: 'permissionMode',
|
|
69
|
+
label: 'Execution policy',
|
|
70
|
+
options: input.permissionModes,
|
|
71
|
+
defaultValue: input.defaultPermissionMode ?? null,
|
|
72
|
+
availability: 'setup',
|
|
73
|
+
liveBehavior: 'none',
|
|
74
|
+
selectionPolicy: 'inherit',
|
|
75
|
+
},
|
|
76
|
+
],
|
|
77
|
+
supportsInterrupt: true,
|
|
78
|
+
streamingTextMode: 'snapshot',
|
|
79
|
+
};
|
|
80
|
+
}
|
|
26
81
|
function normalizeRuntimeTurnState(value) {
|
|
27
82
|
const normalizedTurn = normalizeTurnState(value);
|
|
28
83
|
if (normalizedTurn) {
|
|
@@ -62,6 +117,14 @@ function resolveWorkspaceCwd(config) {
|
|
|
62
117
|
defaultCwd: workingDir,
|
|
63
118
|
});
|
|
64
119
|
}
|
|
120
|
+
function resolveExecutionFallbackReason(environment) {
|
|
121
|
+
if (!environment?.reason || environment.mode !== 'locked') {
|
|
122
|
+
return null;
|
|
123
|
+
}
|
|
124
|
+
return environment.reason === 'Sharing the base workspace (locked mode)'
|
|
125
|
+
? null
|
|
126
|
+
: environment.reason;
|
|
127
|
+
}
|
|
65
128
|
function buildCanonPrompt(input) {
|
|
66
129
|
return buildCanonHostPrompt({
|
|
67
130
|
hostLabel: 'Codex',
|
|
@@ -138,10 +201,27 @@ export async function main() {
|
|
|
138
201
|
const sessions = new Map();
|
|
139
202
|
const pendingSessionCreations = new Map();
|
|
140
203
|
const conversationCache = new Map();
|
|
204
|
+
const knownConversationIds = new Set();
|
|
205
|
+
let lastKnownConversationRefreshAt = 0;
|
|
141
206
|
const { getConversationMeta } = createConversationMetadataLoader({
|
|
142
207
|
client,
|
|
143
208
|
conversationCache,
|
|
144
209
|
});
|
|
210
|
+
function resolveWorkspaceIdForBaseCwd(baseCwd) {
|
|
211
|
+
return workspaceOptions.find((option) => option.cwd === baseCwd)?.id;
|
|
212
|
+
}
|
|
213
|
+
async function refreshKnownConversationIds(force = false) {
|
|
214
|
+
if (!force && Date.now() - lastKnownConversationRefreshAt < HEARTBEAT_MS) {
|
|
215
|
+
return;
|
|
216
|
+
}
|
|
217
|
+
const conversations = await client.getConversations();
|
|
218
|
+
knownConversationIds.clear();
|
|
219
|
+
for (const conversation of conversations) {
|
|
220
|
+
knownConversationIds.add(conversation.id);
|
|
221
|
+
conversationCache.set(conversation.id, conversation);
|
|
222
|
+
}
|
|
223
|
+
lastKnownConversationRefreshAt = Date.now();
|
|
224
|
+
}
|
|
145
225
|
async function loadSenderRuntimeState(conversationId, senderId) {
|
|
146
226
|
try {
|
|
147
227
|
const [turnState, sessionState] = await Promise.all([
|
|
@@ -177,6 +257,7 @@ export async function main() {
|
|
|
177
257
|
cwd: session.cwd,
|
|
178
258
|
executionMode: session.environment.mode,
|
|
179
259
|
...(session.environment.branch ? { executionBranch: session.environment.branch } : {}),
|
|
260
|
+
...(session.environment.worktreePath ? { worktreePath: session.environment.worktreePath } : {}),
|
|
180
261
|
hostMode: true,
|
|
181
262
|
clientType: 'codex',
|
|
182
263
|
state: session.state.state,
|
|
@@ -236,6 +317,7 @@ export async function main() {
|
|
|
236
317
|
}
|
|
237
318
|
}
|
|
238
319
|
async function getOrCreateSession(conversationId) {
|
|
320
|
+
knownConversationIds.add(conversationId);
|
|
239
321
|
const existing = sessions.get(conversationId);
|
|
240
322
|
if (existing && !existing.closed) {
|
|
241
323
|
existing.lastActivity = Date.now();
|
|
@@ -335,6 +417,7 @@ export async function main() {
|
|
|
335
417
|
void runNextTurn(session);
|
|
336
418
|
}
|
|
337
419
|
async function enqueueInboundMessage(input) {
|
|
420
|
+
knownConversationIds.add(input.conversationId);
|
|
338
421
|
let materialized = [];
|
|
339
422
|
if (input.message.id) {
|
|
340
423
|
try {
|
|
@@ -551,13 +634,88 @@ export async function main() {
|
|
|
551
634
|
...(codexPermissionEnvelope.defaultPermissionMode
|
|
552
635
|
? { defaultPermissionMode: codexPermissionEnvelope.defaultPermissionMode }
|
|
553
636
|
: {}),
|
|
637
|
+
runtimeDescriptor: buildCodexRuntimeDescriptor({
|
|
638
|
+
models: [],
|
|
639
|
+
workspaces: buildPublicWorkspaceOptions(workspaceOptions),
|
|
640
|
+
executionModes: hostAvailableExecutionModes,
|
|
641
|
+
permissionModes: [...codexPermissionEnvelope.availablePermissionModes],
|
|
642
|
+
defaultPermissionMode: codexPermissionEnvelope.defaultPermissionMode,
|
|
643
|
+
}),
|
|
554
644
|
};
|
|
555
645
|
const publishRuntimeHeartbeat = async () => {
|
|
556
646
|
if (!streamConnected)
|
|
557
647
|
return;
|
|
648
|
+
await refreshKnownConversationIds().catch((error) => {
|
|
649
|
+
console.error('[canon-codex] Failed to refresh known conversations:', error);
|
|
650
|
+
});
|
|
558
651
|
await publishAgentRuntime(agentId, runtimeDescriptor).catch((error) => {
|
|
559
652
|
console.error('[canon-codex] Failed to publish agent runtime:', error);
|
|
560
653
|
});
|
|
654
|
+
await publishHostSessionSnapshots({
|
|
655
|
+
conversationIds: Array.from(knownConversationIds),
|
|
656
|
+
agentId,
|
|
657
|
+
clientType: 'codex',
|
|
658
|
+
runtime: runtimeDescriptor,
|
|
659
|
+
workspaceOptions,
|
|
660
|
+
defaultCwd: workingDir,
|
|
661
|
+
liveSessionConfigByConversation: new Map(Array.from(sessions.values()).map((session) => {
|
|
662
|
+
const workspaceId = resolveWorkspaceIdForBaseCwd(session.environment.baseCwd);
|
|
663
|
+
return [
|
|
664
|
+
session.conversationId,
|
|
665
|
+
{
|
|
666
|
+
...(session.state.model ? { model: session.state.model } : {}),
|
|
667
|
+
...(workspaceId ? { workspaceId } : {}),
|
|
668
|
+
executionMode: session.environment.mode,
|
|
669
|
+
executionBranch: session.environment.branch ?? null,
|
|
670
|
+
},
|
|
671
|
+
];
|
|
672
|
+
})),
|
|
673
|
+
}).catch((error) => {
|
|
674
|
+
console.error('[canon-codex] Failed to publish session snapshots:', error);
|
|
675
|
+
});
|
|
676
|
+
await Promise.all(Array.from(knownConversationIds).map(async (conversationId) => {
|
|
677
|
+
const session = sessions.get(conversationId);
|
|
678
|
+
const workspaceId = session
|
|
679
|
+
? resolveWorkspaceIdForBaseCwd(session.environment.baseCwd)
|
|
680
|
+
: runtimeDescriptor.defaultWorkspaceId;
|
|
681
|
+
const workspace = workspaceOptions.find((option) => option.id === workspaceId) ?? null;
|
|
682
|
+
const payload = {
|
|
683
|
+
descriptor: runtimeDescriptor.runtimeDescriptor ?? buildCodexRuntimeDescriptor({
|
|
684
|
+
models: runtimeDescriptor.availableModels ?? [],
|
|
685
|
+
workspaces: buildPublicWorkspaceOptions(workspaceOptions),
|
|
686
|
+
executionModes: hostAvailableExecutionModes,
|
|
687
|
+
permissionModes: [...codexPermissionEnvelope.availablePermissionModes],
|
|
688
|
+
defaultPermissionMode: codexPermissionEnvelope.defaultPermissionMode,
|
|
689
|
+
}),
|
|
690
|
+
surfaceMode: 'host',
|
|
691
|
+
statusItems: [
|
|
692
|
+
{
|
|
693
|
+
id: 'transport',
|
|
694
|
+
label: 'Transport',
|
|
695
|
+
value: 'exec --json',
|
|
696
|
+
},
|
|
697
|
+
{
|
|
698
|
+
id: 'streaming',
|
|
699
|
+
label: 'Live output',
|
|
700
|
+
value: 'Thinking, tools, and completed-message previews',
|
|
701
|
+
},
|
|
702
|
+
],
|
|
703
|
+
execution: {
|
|
704
|
+
resolvedWorkspaceLabel: workspace?.label ?? workspaceId ?? null,
|
|
705
|
+
resolvedCwd: session?.cwd ?? workspace?.cwd ?? workingDir,
|
|
706
|
+
executionMode: session?.environment.mode ?? null,
|
|
707
|
+
executionBranch: session?.environment.branch ?? null,
|
|
708
|
+
worktreePath: session?.environment.worktreePath ?? null,
|
|
709
|
+
fallbackReason: resolveExecutionFallbackReason(session?.environment),
|
|
710
|
+
},
|
|
711
|
+
notes: [
|
|
712
|
+
'This Codex host uses the current exec --json transport, so Canon can show thinking, tool activity, and completed assistant-message previews, but not token-by-token text deltas.',
|
|
713
|
+
],
|
|
714
|
+
};
|
|
715
|
+
await writeRuntimeInfo(conversationId, agentId, payload);
|
|
716
|
+
})).catch((error) => {
|
|
717
|
+
console.error('[canon-codex] Failed to publish runtime info:', error);
|
|
718
|
+
});
|
|
561
719
|
};
|
|
562
720
|
const stream = new CanonStream({
|
|
563
721
|
apiKey,
|
|
@@ -599,6 +757,13 @@ export async function main() {
|
|
|
599
757
|
...(codexPermissionEnvelope.defaultPermissionMode
|
|
600
758
|
? { defaultPermissionMode: codexPermissionEnvelope.defaultPermissionMode }
|
|
601
759
|
: {}),
|
|
760
|
+
runtimeDescriptor: buildCodexRuntimeDescriptor({
|
|
761
|
+
models: [],
|
|
762
|
+
workspaces: buildPublicWorkspaceOptions(workspaceOptions),
|
|
763
|
+
executionModes: hostAvailableExecutionModes,
|
|
764
|
+
permissionModes: [...codexPermissionEnvelope.availablePermissionModes],
|
|
765
|
+
defaultPermissionMode: codexPermissionEnvelope.defaultPermissionMode,
|
|
766
|
+
}),
|
|
602
767
|
};
|
|
603
768
|
}
|
|
604
769
|
catch {
|
|
@@ -610,11 +775,21 @@ export async function main() {
|
|
|
610
775
|
...(codexPermissionEnvelope.defaultPermissionMode
|
|
611
776
|
? { defaultPermissionMode: codexPermissionEnvelope.defaultPermissionMode }
|
|
612
777
|
: {}),
|
|
778
|
+
runtimeDescriptor: buildCodexRuntimeDescriptor({
|
|
779
|
+
models: [],
|
|
780
|
+
workspaces: buildPublicWorkspaceOptions(workspaceOptions),
|
|
781
|
+
executionModes: hostAvailableExecutionModes,
|
|
782
|
+
permissionModes: [...codexPermissionEnvelope.availablePermissionModes],
|
|
783
|
+
defaultPermissionMode: codexPermissionEnvelope.defaultPermissionMode,
|
|
784
|
+
}),
|
|
613
785
|
};
|
|
614
786
|
}
|
|
615
787
|
try {
|
|
616
788
|
const conversations = await client.getConversations();
|
|
789
|
+
lastKnownConversationRefreshAt = Date.now();
|
|
617
790
|
for (const conversation of conversations) {
|
|
791
|
+
knownConversationIds.add(conversation.id);
|
|
792
|
+
conversationCache.set(conversation.id, conversation);
|
|
618
793
|
clearStreaming(conversation.id);
|
|
619
794
|
clearSessionState(conversation.id, agentId).catch(() => { });
|
|
620
795
|
clearTurnState(conversation.id, agentId).catch(() => { });
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@canonmsg/codex-plugin",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.7.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": "^0.
|
|
33
|
-
"@canonmsg/core": "^0.
|
|
32
|
+
"@canonmsg/agent-sdk": "^0.9.0",
|
|
33
|
+
"@canonmsg/core": "^0.8.0"
|
|
34
34
|
},
|
|
35
35
|
"engines": {
|
|
36
36
|
"node": ">=18.0.0"
|