@canonmsg/codex-plugin 0.6.6 → 0.9.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 +21 -1
- package/dist/host-runtime.d.ts +8 -2
- package/dist/host-runtime.js +13 -0
- package/dist/host.js +227 -9
- package/dist/setup.js +2 -0
- package/package.json +3 -3
package/README.md
CHANGED
|
@@ -37,7 +37,15 @@ 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 assistant-message snapshots without claiming true token streaming.
|
|
41
|
+
|
|
42
|
+
Current Canon control truth for Codex host mode:
|
|
43
|
+
|
|
44
|
+
- model is live-visible, but current changes apply on the next turn rather than mid-turn
|
|
45
|
+
- workspace selection is setup-only
|
|
46
|
+
- execution mode selection is setup-only
|
|
47
|
+
- the current `Execution policy`/permission choice is setup-only
|
|
48
|
+
- advanced Codex-only controls such as effort, sandbox policy, approval reviewer, or apps/plugins inventory are not exposed on the current transport unless the runtime can actually report them
|
|
41
49
|
|
|
42
50
|
## Working directory
|
|
43
51
|
|
|
@@ -45,12 +53,24 @@ The stable `codex exec --json` surface exposes completed assistant messages and
|
|
|
45
53
|
canon-codex --cwd /path/to/project
|
|
46
54
|
```
|
|
47
55
|
|
|
56
|
+
Advertise multiple project choices to the Canon app:
|
|
57
|
+
|
|
58
|
+
```bash
|
|
59
|
+
canon-codex --cwd ~/dev --workspace-root ~/dev
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
`--cwd` is the default workspace. Each `--workspace-root` value is an approved local root; the host discovers immediate child projects with common markers such as `.git`, `package.json`, `pyproject.toml`, `Cargo.toml`, or `go.mod` and publishes them as selectable projects during session creation. Use repeated `--workspace /path/to/project` entries to advertise specific projects outside those roots. Worktree mode creates a per-conversation git worktree under `~/.canon/conversation-worktrees`; shared-project mode runs directly in the selected directory.
|
|
63
|
+
|
|
64
|
+
If worktree isolation is requested for a project that cannot support it, Canon may fall back to shared-project execution and surface the fallback reason in session details instead of failing the session outright.
|
|
65
|
+
|
|
48
66
|
Useful flags:
|
|
49
67
|
|
|
50
68
|
```bash
|
|
51
69
|
canon-codex --cwd /path/to/project --model gpt-5.4 --full-auto
|
|
52
70
|
```
|
|
53
71
|
|
|
72
|
+
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.
|
|
73
|
+
|
|
54
74
|
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
75
|
|
|
56
76
|
Local smoke test:
|
package/dist/host-runtime.d.ts
CHANGED
|
@@ -97,17 +97,23 @@ export declare function publishHostSessionSnapshots(input: {
|
|
|
97
97
|
liveSessionConfigByConversation?: ReadonlyMap<string, {
|
|
98
98
|
model?: string;
|
|
99
99
|
permissionMode?: string;
|
|
100
|
+
effort?: string;
|
|
101
|
+
runtimeControlValues?: Record<string, string>;
|
|
100
102
|
workspaceId?: string;
|
|
101
103
|
executionMode?: SessionWorkspaceConfig['executionMode'];
|
|
102
104
|
executionBranch?: string | null;
|
|
103
105
|
}>;
|
|
104
106
|
}): Promise<void>;
|
|
105
|
-
export declare function readHostSessionConfig<TExtra extends string = never>(raw: unknown, extraStringFields?: readonly TExtra[]): (SessionWorkspaceConfig & Partial<Record<TExtra, string>>
|
|
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;
|
|
106
110
|
export declare function loadHostSessionConfig<TExtra extends string = never>(input: {
|
|
107
111
|
conversationId: string;
|
|
108
112
|
agentId: string;
|
|
109
113
|
extraStringFields?: readonly TExtra[];
|
|
110
|
-
}): Promise<(SessionWorkspaceConfig & Partial<Record<TExtra, string>>
|
|
114
|
+
}): Promise<(SessionWorkspaceConfig & Partial<Record<TExtra, string>> & {
|
|
115
|
+
runtimeControlValues?: Record<string, string>;
|
|
116
|
+
}) | null>;
|
|
111
117
|
export declare function resolveHostWorkspaceCwd(input: {
|
|
112
118
|
workspaceOptions: HostWorkspaceResolverOption[];
|
|
113
119
|
config: {
|
package/dist/host-runtime.js
CHANGED
|
@@ -162,6 +162,10 @@ export async function publishHostSessionSnapshots(input) {
|
|
|
162
162
|
sessionConfig: {
|
|
163
163
|
...(mergedConfig.model ? { model: mergedConfig.model } : {}),
|
|
164
164
|
...(mergedConfig.permissionMode ? { permissionMode: mergedConfig.permissionMode } : {}),
|
|
165
|
+
...(mergedConfig.effort ? { effort: mergedConfig.effort } : {}),
|
|
166
|
+
...(mergedConfig.runtimeControlValues
|
|
167
|
+
? { runtimeControlValues: mergedConfig.runtimeControlValues }
|
|
168
|
+
: {}),
|
|
165
169
|
...(mergedConfig.workspaceId ? { workspaceId: mergedConfig.workspaceId } : {}),
|
|
166
170
|
...(mergedConfig.executionMode ? { executionMode: mergedConfig.executionMode } : {}),
|
|
167
171
|
},
|
|
@@ -183,6 +187,8 @@ export async function publishHostSessionSnapshots(input) {
|
|
|
183
187
|
hostMode: true,
|
|
184
188
|
model: snapshot.model ?? null,
|
|
185
189
|
permissionMode: snapshot.permissionMode ?? null,
|
|
190
|
+
effort: snapshot.effort ?? null,
|
|
191
|
+
runtimeControlValues: snapshot.runtimeControlValues ?? null,
|
|
186
192
|
workspaceId: snapshot.workspaceId ?? null,
|
|
187
193
|
executionMode: snapshot.executionMode ?? null,
|
|
188
194
|
executionBranch,
|
|
@@ -204,9 +210,16 @@ export function readHostSessionConfig(raw, extraStringFields = []) {
|
|
|
204
210
|
const value = normalizeOptionalString(data[field]);
|
|
205
211
|
return value ? [[field, value]] : [];
|
|
206
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
|
+
}));
|
|
207
219
|
return {
|
|
208
220
|
...(baseConfig ?? {}),
|
|
209
221
|
...extraConfig,
|
|
222
|
+
...(Object.keys(runtimeControlValues).length > 0 ? { runtimeControlValues } : {}),
|
|
210
223
|
};
|
|
211
224
|
}
|
|
212
225
|
export async function loadHostSessionConfig(input) {
|
package/dist/host.js
CHANGED
|
@@ -3,7 +3,7 @@ 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 {
|
|
6
|
+
import { buildConfiguredWorkspaceOptionsWithRoots, buildPublicWorkspaceRoots, 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
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';
|
|
@@ -23,6 +23,93 @@ const CODEX_RUNTIME_CAPABILITIES = {
|
|
|
23
23
|
};
|
|
24
24
|
let workingDir = process.cwd();
|
|
25
25
|
let workspaceOptions = [];
|
|
26
|
+
let workspaceRoots = [];
|
|
27
|
+
let workspaceRootMetadata = [];
|
|
28
|
+
function buildCodexRuntimeDescriptor(input) {
|
|
29
|
+
return {
|
|
30
|
+
coreControls: [
|
|
31
|
+
{
|
|
32
|
+
id: 'model',
|
|
33
|
+
label: 'Model',
|
|
34
|
+
options: input.models,
|
|
35
|
+
defaultValue: input.models[0]?.value ?? null,
|
|
36
|
+
availability: 'setup_and_live',
|
|
37
|
+
liveBehavior: 'next_turn',
|
|
38
|
+
selectionPolicy: 'inherit',
|
|
39
|
+
},
|
|
40
|
+
{
|
|
41
|
+
id: 'workspace',
|
|
42
|
+
label: 'Project',
|
|
43
|
+
options: input.workspaces.map((workspace) => ({
|
|
44
|
+
value: workspace.id,
|
|
45
|
+
label: workspace.label,
|
|
46
|
+
...(workspace.description ? { description: workspace.description } : {}),
|
|
47
|
+
...(workspace.workspaceRootId ? { workspaceRootId: workspace.workspaceRootId } : {}),
|
|
48
|
+
...(workspace.workspaceRelativePath ? { workspaceRelativePath: workspace.workspaceRelativePath } : {}),
|
|
49
|
+
...(workspace.source ? { source: workspace.source } : {}),
|
|
50
|
+
})),
|
|
51
|
+
defaultValue: input.workspaces[0]?.id ?? null,
|
|
52
|
+
availability: 'setup',
|
|
53
|
+
liveBehavior: 'none',
|
|
54
|
+
selectionPolicy: 'inherit',
|
|
55
|
+
description: input.workspaceRoots?.length
|
|
56
|
+
? 'Choose one of the projects discovered inside the approved local roots for this host.'
|
|
57
|
+
: 'Choose one of the local projects advertised by this host.',
|
|
58
|
+
},
|
|
59
|
+
{
|
|
60
|
+
id: 'executionMode',
|
|
61
|
+
label: 'Execution mode',
|
|
62
|
+
options: input.executionModes.map((mode) => ({
|
|
63
|
+
value: mode,
|
|
64
|
+
label: mode === 'worktree' ? 'Isolated worktree' : 'Use shared project',
|
|
65
|
+
description: mode === 'worktree'
|
|
66
|
+
? 'Creates or reuses a per-conversation git worktree under ~/.canon/conversation-worktrees when the selected project is a git repo.'
|
|
67
|
+
: 'Runs directly in the selected project folder. Changes happen there.',
|
|
68
|
+
})),
|
|
69
|
+
defaultValue: null,
|
|
70
|
+
availability: 'setup',
|
|
71
|
+
liveBehavior: 'none',
|
|
72
|
+
selectionPolicy: 'required_explicit',
|
|
73
|
+
},
|
|
74
|
+
],
|
|
75
|
+
runtimeControls: [
|
|
76
|
+
{
|
|
77
|
+
id: 'permissionMode',
|
|
78
|
+
label: 'Execution policy',
|
|
79
|
+
options: input.permissionModes,
|
|
80
|
+
defaultValue: input.defaultPermissionMode ?? null,
|
|
81
|
+
availability: 'setup',
|
|
82
|
+
liveBehavior: 'none',
|
|
83
|
+
selectionPolicy: 'inherit',
|
|
84
|
+
},
|
|
85
|
+
],
|
|
86
|
+
actions: [
|
|
87
|
+
{
|
|
88
|
+
id: 'stop',
|
|
89
|
+
label: 'Stop',
|
|
90
|
+
description: 'Interrupt the current Codex exec turn.',
|
|
91
|
+
aliases: ['stop'],
|
|
92
|
+
category: 'turn',
|
|
93
|
+
placements: ['composer_slash', 'command_palette'],
|
|
94
|
+
availability: ['busy'],
|
|
95
|
+
dispatch: { kind: 'signal', signal: 'interrupt' },
|
|
96
|
+
},
|
|
97
|
+
{
|
|
98
|
+
id: 'stop-and-clear-queue',
|
|
99
|
+
label: 'Stop & clear queue',
|
|
100
|
+
description: 'Interrupt the current Codex exec turn and drop queued Canon messages.',
|
|
101
|
+
aliases: ['stop-clear', 'clear-queue'],
|
|
102
|
+
category: 'turn',
|
|
103
|
+
placements: ['composer_slash', 'command_palette', 'session_strip'],
|
|
104
|
+
availability: ['busy_with_queue'],
|
|
105
|
+
dispatch: { kind: 'signal', signal: 'stop_and_drop' },
|
|
106
|
+
},
|
|
107
|
+
],
|
|
108
|
+
workspaceRoots: input.workspaceRoots,
|
|
109
|
+
supportsInterrupt: true,
|
|
110
|
+
streamingTextMode: 'snapshot',
|
|
111
|
+
};
|
|
112
|
+
}
|
|
26
113
|
function normalizeRuntimeTurnState(value) {
|
|
27
114
|
const normalizedTurn = normalizeTurnState(value);
|
|
28
115
|
if (normalizedTurn) {
|
|
@@ -49,11 +136,10 @@ async function loadSessionConfig(conversationId, agentId) {
|
|
|
49
136
|
extraStringFields: ['permissionMode'],
|
|
50
137
|
});
|
|
51
138
|
}
|
|
52
|
-
// Default to 'locked' (shared workspace) when no mode has been picked. The
|
|
53
|
-
// UI still lets owners flip to 'worktree'; this just stops sessions from
|
|
54
|
-
// failing closed when the mode has never been written.
|
|
55
139
|
function resolveSessionExecutionMode(config) {
|
|
56
|
-
|
|
140
|
+
if (config?.executionMode)
|
|
141
|
+
return config.executionMode;
|
|
142
|
+
throw new ExecutionEnvironmentError('Session config is missing an execution mode.', 'Choose Isolated worktree or Use shared project before starting this coding session.');
|
|
57
143
|
}
|
|
58
144
|
function resolveWorkspaceCwd(config) {
|
|
59
145
|
return resolveHostWorkspaceCwd({
|
|
@@ -62,6 +148,14 @@ function resolveWorkspaceCwd(config) {
|
|
|
62
148
|
defaultCwd: workingDir,
|
|
63
149
|
});
|
|
64
150
|
}
|
|
151
|
+
function resolveExecutionFallbackReason(environment) {
|
|
152
|
+
if (!environment?.reason || environment.mode !== 'locked') {
|
|
153
|
+
return null;
|
|
154
|
+
}
|
|
155
|
+
return environment.reason === 'Sharing the base workspace (locked mode)'
|
|
156
|
+
? null
|
|
157
|
+
: environment.reason;
|
|
158
|
+
}
|
|
65
159
|
function buildCanonPrompt(input) {
|
|
66
160
|
return buildCanonHostPrompt({
|
|
67
161
|
hostLabel: 'Codex',
|
|
@@ -101,6 +195,7 @@ export async function main() {
|
|
|
101
195
|
'codex-profile': { type: 'string' },
|
|
102
196
|
'add-dir': { type: 'string', multiple: true },
|
|
103
197
|
workspace: { type: 'string', multiple: true },
|
|
198
|
+
'workspace-root': { type: 'string', multiple: true },
|
|
104
199
|
config: { type: 'string', multiple: true },
|
|
105
200
|
'codex-bin': { type: 'string' },
|
|
106
201
|
'full-auto': { type: 'boolean' },
|
|
@@ -109,7 +204,17 @@ export async function main() {
|
|
|
109
204
|
strict: true,
|
|
110
205
|
});
|
|
111
206
|
workingDir = (typeof args.cwd === 'string' ? args.cwd : null) || process.cwd();
|
|
112
|
-
|
|
207
|
+
const workspaceDiscovery = buildConfiguredWorkspaceOptionsWithRoots({
|
|
208
|
+
primaryCwd: workingDir,
|
|
209
|
+
configuredWorkspaces: args.workspace ?? [],
|
|
210
|
+
workspaceRoots: args['workspace-root'] ?? [],
|
|
211
|
+
});
|
|
212
|
+
workspaceOptions = workspaceDiscovery.workspaceOptions;
|
|
213
|
+
workspaceRoots = workspaceDiscovery.workspaceRoots;
|
|
214
|
+
workspaceRootMetadata = buildPublicWorkspaceRoots(workspaceRoots);
|
|
215
|
+
for (const warning of workspaceDiscovery.warnings) {
|
|
216
|
+
console.error(`[canon-codex] ${warning}`);
|
|
217
|
+
}
|
|
113
218
|
if (typeof args['ask-for-approval'] === 'string') {
|
|
114
219
|
console.error('[canon-codex] Note: newer Codex CLI releases do not accept --ask-for-approval for `codex exec`; Canon will translate compatible legacy usage when possible.');
|
|
115
220
|
}
|
|
@@ -194,6 +299,10 @@ export async function main() {
|
|
|
194
299
|
cwd: session.cwd,
|
|
195
300
|
executionMode: session.environment.mode,
|
|
196
301
|
...(session.environment.branch ? { executionBranch: session.environment.branch } : {}),
|
|
302
|
+
...(session.environment.worktreePath ? { worktreePath: session.environment.worktreePath } : {}),
|
|
303
|
+
...(resolveExecutionFallbackReason(session.environment)
|
|
304
|
+
? { executionFallbackReason: resolveExecutionFallbackReason(session.environment) ?? undefined }
|
|
305
|
+
: {}),
|
|
197
306
|
hostMode: true,
|
|
198
307
|
clientType: 'codex',
|
|
199
308
|
state: session.state.state,
|
|
@@ -227,11 +336,33 @@ export async function main() {
|
|
|
227
336
|
clearStreaming(conversationId);
|
|
228
337
|
client.setTyping(conversationId, false).catch(() => { });
|
|
229
338
|
}
|
|
339
|
+
function refreshVisibleWorkSignal(session) {
|
|
340
|
+
if (!session.running || session.closed)
|
|
341
|
+
return;
|
|
342
|
+
if (session.turnState !== 'thinking' && session.turnState !== 'tool')
|
|
343
|
+
return;
|
|
344
|
+
client.setTyping(session.conversationId, true, 'thinking').catch(() => { });
|
|
345
|
+
}
|
|
346
|
+
function startVisibleWorkSignal(session) {
|
|
347
|
+
refreshVisibleWorkSignal(session);
|
|
348
|
+
if (session.typingKeepaliveTimer)
|
|
349
|
+
return;
|
|
350
|
+
session.typingKeepaliveTimer = setInterval(() => {
|
|
351
|
+
refreshVisibleWorkSignal(session);
|
|
352
|
+
}, 3500);
|
|
353
|
+
}
|
|
354
|
+
function stopVisibleWorkSignal(session) {
|
|
355
|
+
if (session.typingKeepaliveTimer) {
|
|
356
|
+
clearInterval(session.typingKeepaliveTimer);
|
|
357
|
+
session.typingKeepaliveTimer = null;
|
|
358
|
+
}
|
|
359
|
+
}
|
|
230
360
|
function closeSession(conversationId) {
|
|
231
361
|
const session = sessions.get(conversationId);
|
|
232
362
|
if (!session)
|
|
233
363
|
return;
|
|
234
364
|
session.closed = true;
|
|
365
|
+
stopVisibleWorkSignal(session);
|
|
235
366
|
releaseConversationEnvironment(session.environment);
|
|
236
367
|
clearStreaming(conversationId);
|
|
237
368
|
clearSessionState(conversationId, agentId).catch(() => { });
|
|
@@ -319,6 +450,7 @@ export async function main() {
|
|
|
319
450
|
currentTurnOpenedAt: null,
|
|
320
451
|
lastAcceptedIntent: null,
|
|
321
452
|
lastActivity: Date.now(),
|
|
453
|
+
typingKeepaliveTimer: null,
|
|
322
454
|
closed: false,
|
|
323
455
|
};
|
|
324
456
|
sessions.set(conversationId, session);
|
|
@@ -398,7 +530,12 @@ export async function main() {
|
|
|
398
530
|
const message = error instanceof Error ? error.message : String(error);
|
|
399
531
|
const userMessage = error instanceof ExecutionEnvironmentError ? error.userMessage : message;
|
|
400
532
|
console.error(`[canon-codex] [${input.conversationId.slice(0, 8)}] Failed to create session: ${message}`);
|
|
401
|
-
await client.sendMessage(input.conversationId, `I couldn't start a coding session for this workspace: ${userMessage}
|
|
533
|
+
await client.sendMessage(input.conversationId, `I couldn't start a coding session for this workspace: ${userMessage}`, {
|
|
534
|
+
metadata: {
|
|
535
|
+
turnSemantics: 'turn_complete',
|
|
536
|
+
turnComplete: true,
|
|
537
|
+
},
|
|
538
|
+
}).catch(() => { });
|
|
402
539
|
return;
|
|
403
540
|
}
|
|
404
541
|
const turnMetadata = normalizeTurnMetadata(input.message.metadata);
|
|
@@ -439,7 +576,7 @@ export async function main() {
|
|
|
439
576
|
await markQueuedMessageAccepted(session.conversationId, nextTurn.sourceMessageId, nextTurn.markAccepted);
|
|
440
577
|
writeState(session);
|
|
441
578
|
writeTurn(session);
|
|
442
|
-
|
|
579
|
+
startVisibleWorkSignal(session);
|
|
443
580
|
rtdbWrite(`/streaming/${session.conversationId}/${agentId}`, {
|
|
444
581
|
text: 'Thinking…',
|
|
445
582
|
status: 'thinking',
|
|
@@ -457,6 +594,8 @@ export async function main() {
|
|
|
457
594
|
if (event.type === 'message') {
|
|
458
595
|
session.turnState = 'streaming';
|
|
459
596
|
writeTurn(session);
|
|
597
|
+
stopVisibleWorkSignal(session);
|
|
598
|
+
client.setTyping(session.conversationId, false).catch(() => { });
|
|
460
599
|
rtdbWrite(`/streaming/${session.conversationId}/${agentId}`, {
|
|
461
600
|
text: event.text,
|
|
462
601
|
status: 'streaming',
|
|
@@ -467,7 +606,7 @@ export async function main() {
|
|
|
467
606
|
if (event.type === 'command.started') {
|
|
468
607
|
session.turnState = 'tool';
|
|
469
608
|
writeTurn(session);
|
|
470
|
-
|
|
609
|
+
startVisibleWorkSignal(session);
|
|
471
610
|
rtdbWrite(`/streaming/${session.conversationId}/${agentId}`, {
|
|
472
611
|
text: summarizeCommand(event.command),
|
|
473
612
|
status: 'tool',
|
|
@@ -519,6 +658,7 @@ export async function main() {
|
|
|
519
658
|
else if (result.interrupted) {
|
|
520
659
|
session.turnState = 'interrupted';
|
|
521
660
|
writeTurn(session);
|
|
661
|
+
stopVisibleWorkSignal(session);
|
|
522
662
|
clearStreaming(session.conversationId);
|
|
523
663
|
client.setTyping(session.conversationId, false).catch(() => { });
|
|
524
664
|
console.error(`[canon-codex] [${session.conversationId.slice(0, 8)}] Turn interrupted`);
|
|
@@ -541,6 +681,7 @@ export async function main() {
|
|
|
541
681
|
console.error(`[canon-codex] [${session.conversationId.slice(0, 8)}] Turn failed:`, error);
|
|
542
682
|
}
|
|
543
683
|
finally {
|
|
684
|
+
stopVisibleWorkSignal(session);
|
|
544
685
|
session.running = false;
|
|
545
686
|
session.state.state = 'idle';
|
|
546
687
|
session.turnState = 'idle';
|
|
@@ -570,6 +711,14 @@ export async function main() {
|
|
|
570
711
|
...(codexPermissionEnvelope.defaultPermissionMode
|
|
571
712
|
? { defaultPermissionMode: codexPermissionEnvelope.defaultPermissionMode }
|
|
572
713
|
: {}),
|
|
714
|
+
runtimeDescriptor: buildCodexRuntimeDescriptor({
|
|
715
|
+
models: [],
|
|
716
|
+
workspaces: buildPublicWorkspaceOptions(workspaceOptions),
|
|
717
|
+
workspaceRoots: workspaceRootMetadata,
|
|
718
|
+
executionModes: hostAvailableExecutionModes,
|
|
719
|
+
permissionModes: [...codexPermissionEnvelope.availablePermissionModes],
|
|
720
|
+
defaultPermissionMode: codexPermissionEnvelope.defaultPermissionMode,
|
|
721
|
+
}),
|
|
573
722
|
};
|
|
574
723
|
const publishRuntimeHeartbeat = async () => {
|
|
575
724
|
if (!streamConnected)
|
|
@@ -602,6 +751,59 @@ export async function main() {
|
|
|
602
751
|
}).catch((error) => {
|
|
603
752
|
console.error('[canon-codex] Failed to publish session snapshots:', error);
|
|
604
753
|
});
|
|
754
|
+
await Promise.all(Array.from(knownConversationIds).map(async (conversationId) => {
|
|
755
|
+
const session = sessions.get(conversationId);
|
|
756
|
+
const workspaceId = session
|
|
757
|
+
? resolveWorkspaceIdForBaseCwd(session.environment.baseCwd)
|
|
758
|
+
: runtimeDescriptor.defaultWorkspaceId;
|
|
759
|
+
const workspace = workspaceOptions.find((option) => option.id === workspaceId) ?? null;
|
|
760
|
+
const payload = {
|
|
761
|
+
descriptor: runtimeDescriptor.runtimeDescriptor ?? buildCodexRuntimeDescriptor({
|
|
762
|
+
models: runtimeDescriptor.availableModels ?? [],
|
|
763
|
+
workspaces: buildPublicWorkspaceOptions(workspaceOptions),
|
|
764
|
+
workspaceRoots: workspaceRootMetadata,
|
|
765
|
+
executionModes: hostAvailableExecutionModes,
|
|
766
|
+
permissionModes: [...codexPermissionEnvelope.availablePermissionModes],
|
|
767
|
+
defaultPermissionMode: codexPermissionEnvelope.defaultPermissionMode,
|
|
768
|
+
}),
|
|
769
|
+
surfaceMode: 'host',
|
|
770
|
+
statusItems: [
|
|
771
|
+
{
|
|
772
|
+
id: 'transport',
|
|
773
|
+
label: 'Transport',
|
|
774
|
+
value: 'exec --json',
|
|
775
|
+
},
|
|
776
|
+
{
|
|
777
|
+
id: 'streaming',
|
|
778
|
+
label: 'Live output',
|
|
779
|
+
value: 'Thinking, tools, and completed-message previews',
|
|
780
|
+
},
|
|
781
|
+
{
|
|
782
|
+
id: 'nativeActions',
|
|
783
|
+
label: 'Native actions',
|
|
784
|
+
value: 'Limited until app-server transport',
|
|
785
|
+
tone: 'warning',
|
|
786
|
+
},
|
|
787
|
+
],
|
|
788
|
+
execution: {
|
|
789
|
+
resolvedWorkspaceLabel: workspace?.label ?? workspaceId ?? null,
|
|
790
|
+
resolvedCwd: session?.cwd ?? workspace?.cwd ?? workingDir,
|
|
791
|
+
workspaceRootId: workspace?.workspaceRootId ?? null,
|
|
792
|
+
workspaceRelativePath: workspace?.workspaceRelativePath ?? null,
|
|
793
|
+
executionMode: session?.environment.mode ?? null,
|
|
794
|
+
executionBranch: session?.environment.branch ?? null,
|
|
795
|
+
worktreePath: session?.environment.worktreePath ?? null,
|
|
796
|
+
fallbackReason: resolveExecutionFallbackReason(session?.environment),
|
|
797
|
+
},
|
|
798
|
+
notes: [
|
|
799
|
+
'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.',
|
|
800
|
+
'Codex review, compact/rollback, live plan/diff/reasoning updates, PTY command execution, plugin/app/MCP inventory, and structured approvals require the future app-server transport.',
|
|
801
|
+
],
|
|
802
|
+
};
|
|
803
|
+
await writeRuntimeInfo(conversationId, agentId, payload);
|
|
804
|
+
})).catch((error) => {
|
|
805
|
+
console.error('[canon-codex] Failed to publish runtime info:', error);
|
|
806
|
+
});
|
|
605
807
|
};
|
|
606
808
|
const stream = new CanonStream({
|
|
607
809
|
apiKey,
|
|
@@ -643,6 +845,14 @@ export async function main() {
|
|
|
643
845
|
...(codexPermissionEnvelope.defaultPermissionMode
|
|
644
846
|
? { defaultPermissionMode: codexPermissionEnvelope.defaultPermissionMode }
|
|
645
847
|
: {}),
|
|
848
|
+
runtimeDescriptor: buildCodexRuntimeDescriptor({
|
|
849
|
+
models: [],
|
|
850
|
+
workspaces: buildPublicWorkspaceOptions(workspaceOptions),
|
|
851
|
+
workspaceRoots: workspaceRootMetadata,
|
|
852
|
+
executionModes: hostAvailableExecutionModes,
|
|
853
|
+
permissionModes: [...codexPermissionEnvelope.availablePermissionModes],
|
|
854
|
+
defaultPermissionMode: codexPermissionEnvelope.defaultPermissionMode,
|
|
855
|
+
}),
|
|
646
856
|
};
|
|
647
857
|
}
|
|
648
858
|
catch {
|
|
@@ -654,6 +864,14 @@ export async function main() {
|
|
|
654
864
|
...(codexPermissionEnvelope.defaultPermissionMode
|
|
655
865
|
? { defaultPermissionMode: codexPermissionEnvelope.defaultPermissionMode }
|
|
656
866
|
: {}),
|
|
867
|
+
runtimeDescriptor: buildCodexRuntimeDescriptor({
|
|
868
|
+
models: [],
|
|
869
|
+
workspaces: buildPublicWorkspaceOptions(workspaceOptions),
|
|
870
|
+
workspaceRoots: workspaceRootMetadata,
|
|
871
|
+
executionModes: hostAvailableExecutionModes,
|
|
872
|
+
permissionModes: [...codexPermissionEnvelope.availablePermissionModes],
|
|
873
|
+
defaultPermissionMode: codexPermissionEnvelope.defaultPermissionMode,
|
|
874
|
+
}),
|
|
657
875
|
};
|
|
658
876
|
}
|
|
659
877
|
try {
|
package/dist/setup.js
CHANGED
|
@@ -21,8 +21,10 @@ export function main() {
|
|
|
21
21
|
console.log('');
|
|
22
22
|
console.log(' 2. Start the host in a project directory and keep it running');
|
|
23
23
|
console.log(' canon-codex --cwd /path/to/project');
|
|
24
|
+
console.log(' canon-codex --cwd ~/dev --workspace-root ~/dev');
|
|
24
25
|
console.log('');
|
|
25
26
|
console.log(' A git repo is not required; any readable directory works.');
|
|
27
|
+
console.log(' Use --workspace-root to let Canon offer discovered projects inside an approved root.');
|
|
26
28
|
console.log('');
|
|
27
29
|
console.log('Optional flags:');
|
|
28
30
|
console.log(' --model gpt-5.4');
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@canonmsg/codex-plugin",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.9.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.2",
|
|
33
|
+
"@canonmsg/core": "^0.10.0"
|
|
34
34
|
},
|
|
35
35
|
"engines": {
|
|
36
36
|
"node": ">=18.0.0"
|