@canonmsg/codex-plugin 0.7.0 → 0.9.1
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 +23 -5
- package/dist/host-runtime.js +0 -2
- package/dist/host.js +200 -52
- package/dist/register.js +23 -18
- package/dist/session-store.d.ts +4 -3
- package/dist/session-store.js +32 -6
- package/dist/setup.js +2 -0
- package/package.json +3 -3
package/README.md
CHANGED
|
@@ -22,7 +22,15 @@ 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
|
-
If the terminal closes or the machine restarts, the agent goes offline until you start the host again.
|
|
25
|
+
If the terminal closes or the machine restarts, the agent goes offline until you start the host again. 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
|
+
|
|
27
|
+
```bash
|
|
28
|
+
npm install -g @canonmsg/local-agents
|
|
29
|
+
canon-necromance
|
|
30
|
+
canon-necromance revive my-codex
|
|
31
|
+
```
|
|
32
|
+
|
|
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`. Keep the revived terminal open; closing it takes this local agent offline.
|
|
26
34
|
|
|
27
35
|
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.
|
|
28
36
|
|
|
@@ -37,7 +45,15 @@ You do not need a git repo for host mode. The plugin passes `--skip-git-repo-che
|
|
|
37
45
|
|
|
38
46
|
## Current limitation
|
|
39
47
|
|
|
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.
|
|
48
|
+
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.
|
|
49
|
+
|
|
50
|
+
Current Canon control truth for Codex host mode:
|
|
51
|
+
|
|
52
|
+
- model is live-visible, but current changes apply on the next turn rather than mid-turn
|
|
53
|
+
- workspace selection is setup-only
|
|
54
|
+
- execution mode selection is setup-only
|
|
55
|
+
- the current `Execution policy`/permission choice is setup-only
|
|
56
|
+
- 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
57
|
|
|
42
58
|
## Working directory
|
|
43
59
|
|
|
@@ -48,10 +64,12 @@ canon-codex --cwd /path/to/project
|
|
|
48
64
|
Advertise multiple project choices to the Canon app:
|
|
49
65
|
|
|
50
66
|
```bash
|
|
51
|
-
canon-codex --cwd ~/dev --workspace ~/dev
|
|
67
|
+
canon-codex --cwd ~/dev --workspace-root ~/dev
|
|
52
68
|
```
|
|
53
69
|
|
|
54
|
-
`--cwd` is the default workspace. Each `--workspace` value
|
|
70
|
+
`--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.
|
|
71
|
+
|
|
72
|
+
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.
|
|
55
73
|
|
|
56
74
|
Useful flags:
|
|
57
75
|
|
|
@@ -89,7 +107,7 @@ If `canon-codex` starts but cannot find the `codex` binary, either fix your `PAT
|
|
|
89
107
|
canon-codex --cwd /path/to/project --codex-bin /absolute/path/to/codex
|
|
90
108
|
```
|
|
91
109
|
|
|
92
|
-
If Canon rejects authenticated requests with `401 Invalid API key`, the stored Canon profile needs a fresh key.
|
|
110
|
+
If Canon rejects authenticated requests with `401 Invalid API key`, the stored Canon profile needs a fresh key. Reconnect the same profile to overwrite `~/.canon/agents.json`, then restart or revive the host:
|
|
93
111
|
|
|
94
112
|
```bash
|
|
95
113
|
canon-codex-register --name "My Codex" --description "My local coding agent" --phone "+15551234567" --profile my-codex
|
package/dist/host-runtime.js
CHANGED
|
@@ -68,8 +68,6 @@ export function renderCanonHostInboundContent(message, materialized) {
|
|
|
68
68
|
}
|
|
69
69
|
function describeContactCard(card) {
|
|
70
70
|
const parts = [`${card.userType} · userId: ${card.userId}`];
|
|
71
|
-
if (card.accessLevel)
|
|
72
|
-
parts.push(`access: ${card.accessLevel}`);
|
|
73
71
|
if (card.ownerName)
|
|
74
72
|
parts.push(`owner: ${card.ownerName}`);
|
|
75
73
|
if (card.about)
|
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, getActiveProfileLock, initRTDBAuth, buildLocalRuntimeId, heartbeatLocalRuntimeEntry, loadRuntimeSessionState, markLocalRuntimeStopped, normalizeTurnMetadata, normalizeTurnState, prepareConversationEnvironment, releaseConversationEnvironment, resolveCanonAgent, rtdbRead, rtdbWrite, writeRuntimeInfo, shouldTriggerAgentTurn, saveRuntimeSessionState, writeSessionState, writeTurnState, upsertLocalRuntimeEntry, } 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,8 @@ const CODEX_RUNTIME_CAPABILITIES = {
|
|
|
23
23
|
};
|
|
24
24
|
let workingDir = process.cwd();
|
|
25
25
|
let workspaceOptions = [];
|
|
26
|
+
let workspaceRoots = [];
|
|
27
|
+
let workspaceRootMetadata = [];
|
|
26
28
|
function buildCodexRuntimeDescriptor(input) {
|
|
27
29
|
return {
|
|
28
30
|
coreControls: [
|
|
@@ -37,25 +39,32 @@ function buildCodexRuntimeDescriptor(input) {
|
|
|
37
39
|
},
|
|
38
40
|
{
|
|
39
41
|
id: 'workspace',
|
|
40
|
-
label: '
|
|
42
|
+
label: 'Project',
|
|
41
43
|
options: input.workspaces.map((workspace) => ({
|
|
42
44
|
value: workspace.id,
|
|
43
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 } : {}),
|
|
44
50
|
})),
|
|
45
51
|
defaultValue: input.workspaces[0]?.id ?? null,
|
|
46
52
|
availability: 'setup',
|
|
47
53
|
liveBehavior: 'none',
|
|
48
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.',
|
|
49
58
|
},
|
|
50
59
|
{
|
|
51
60
|
id: 'executionMode',
|
|
52
61
|
label: 'Execution mode',
|
|
53
62
|
options: input.executionModes.map((mode) => ({
|
|
54
63
|
value: mode,
|
|
55
|
-
label: mode === 'worktree' ? 'Isolated worktree' : 'Use shared
|
|
64
|
+
label: mode === 'worktree' ? 'Isolated worktree' : 'Use shared project',
|
|
56
65
|
description: mode === 'worktree'
|
|
57
|
-
? 'Creates or reuses a per-conversation git worktree under ~/.canon/conversation-worktrees when the selected
|
|
58
|
-
: 'Runs directly in the selected
|
|
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.',
|
|
59
68
|
})),
|
|
60
69
|
defaultValue: null,
|
|
61
70
|
availability: 'setup',
|
|
@@ -74,6 +83,29 @@ function buildCodexRuntimeDescriptor(input) {
|
|
|
74
83
|
selectionPolicy: 'inherit',
|
|
75
84
|
},
|
|
76
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,
|
|
77
109
|
supportsInterrupt: true,
|
|
78
110
|
streamingTextMode: 'snapshot',
|
|
79
111
|
};
|
|
@@ -104,11 +136,10 @@ async function loadSessionConfig(conversationId, agentId) {
|
|
|
104
136
|
extraStringFields: ['permissionMode'],
|
|
105
137
|
});
|
|
106
138
|
}
|
|
107
|
-
// Default to 'locked' (shared workspace) when no mode has been picked. The
|
|
108
|
-
// UI still lets owners flip to 'worktree'; this just stops sessions from
|
|
109
|
-
// failing closed when the mode has never been written.
|
|
110
139
|
function resolveSessionExecutionMode(config) {
|
|
111
|
-
|
|
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.');
|
|
112
143
|
}
|
|
113
144
|
function resolveWorkspaceCwd(config) {
|
|
114
145
|
return resolveHostWorkspaceCwd({
|
|
@@ -164,6 +195,7 @@ export async function main() {
|
|
|
164
195
|
'codex-profile': { type: 'string' },
|
|
165
196
|
'add-dir': { type: 'string', multiple: true },
|
|
166
197
|
workspace: { type: 'string', multiple: true },
|
|
198
|
+
'workspace-root': { type: 'string', multiple: true },
|
|
167
199
|
config: { type: 'string', multiple: true },
|
|
168
200
|
'codex-bin': { type: 'string' },
|
|
169
201
|
'full-auto': { type: 'boolean' },
|
|
@@ -172,13 +204,23 @@ export async function main() {
|
|
|
172
204
|
strict: true,
|
|
173
205
|
});
|
|
174
206
|
workingDir = (typeof args.cwd === 'string' ? args.cwd : null) || process.cwd();
|
|
175
|
-
|
|
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
|
+
}
|
|
176
218
|
if (typeof args['ask-for-approval'] === 'string') {
|
|
177
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.');
|
|
178
220
|
}
|
|
179
|
-
const { apiKey, agentId: profileAgentId, profile } = resolveCanonAgent({ logPrefix: '[canon-codex]' });
|
|
221
|
+
const { apiKey, agentId: profileAgentId, agentName: profileAgentName, profile, baseUrl, lockHandle, } = resolveCanonAgent({ logPrefix: '[canon-codex]', expectedClientType: 'codex' });
|
|
180
222
|
console.error(`[canon-codex] Starting${profile ? ` (profile: ${profile})` : ''} in ${workingDir}`);
|
|
181
|
-
const client = new CanonClient(apiKey);
|
|
223
|
+
const client = new CanonClient(apiKey, baseUrl);
|
|
182
224
|
initRTDBAuth(client);
|
|
183
225
|
let agentId;
|
|
184
226
|
let ownerId = null;
|
|
@@ -198,6 +240,34 @@ export async function main() {
|
|
|
198
240
|
}
|
|
199
241
|
console.error(`[canon-codex] Authenticated as ${agentId}`);
|
|
200
242
|
}
|
|
243
|
+
const launchArgs = [...process.argv.slice(2)];
|
|
244
|
+
if (!launchArgs.some((arg) => arg === '--cwd' || arg.startsWith('--cwd='))) {
|
|
245
|
+
launchArgs.push('--cwd', workingDir);
|
|
246
|
+
}
|
|
247
|
+
const runtimeId = buildLocalRuntimeId({
|
|
248
|
+
runtime: 'codex',
|
|
249
|
+
profile,
|
|
250
|
+
cwd: workingDir,
|
|
251
|
+
launchCommand: ['canon-codex', ...launchArgs],
|
|
252
|
+
});
|
|
253
|
+
upsertLocalRuntimeEntry({
|
|
254
|
+
id: runtimeId,
|
|
255
|
+
runtime: 'codex',
|
|
256
|
+
profile,
|
|
257
|
+
agentId,
|
|
258
|
+
agentName: profileAgentName,
|
|
259
|
+
cwd: workingDir,
|
|
260
|
+
baseCwd: workingDir,
|
|
261
|
+
workspaceRoots: workspaceRoots.map((root) => root.cwd),
|
|
262
|
+
workspaces: workspaceOptions.map((workspace) => workspace.cwd),
|
|
263
|
+
launchCommand: ['canon-codex', ...launchArgs],
|
|
264
|
+
pid: process.pid,
|
|
265
|
+
status: profile ? 'running' : 'manual',
|
|
266
|
+
reviveCapability: profile ? 'revivable' : 'manual',
|
|
267
|
+
surfaceMode: 'host',
|
|
268
|
+
lastStartedAt: new Date().toISOString(),
|
|
269
|
+
lastHeartbeatAt: new Date().toISOString(),
|
|
270
|
+
});
|
|
201
271
|
const sessions = new Map();
|
|
202
272
|
const pendingSessionCreations = new Map();
|
|
203
273
|
const conversationCache = new Map();
|
|
@@ -258,6 +328,9 @@ export async function main() {
|
|
|
258
328
|
executionMode: session.environment.mode,
|
|
259
329
|
...(session.environment.branch ? { executionBranch: session.environment.branch } : {}),
|
|
260
330
|
...(session.environment.worktreePath ? { worktreePath: session.environment.worktreePath } : {}),
|
|
331
|
+
...(resolveExecutionFallbackReason(session.environment)
|
|
332
|
+
? { executionFallbackReason: resolveExecutionFallbackReason(session.environment) ?? undefined }
|
|
333
|
+
: {}),
|
|
261
334
|
hostMode: true,
|
|
262
335
|
clientType: 'codex',
|
|
263
336
|
state: session.state.state,
|
|
@@ -291,11 +364,33 @@ export async function main() {
|
|
|
291
364
|
clearStreaming(conversationId);
|
|
292
365
|
client.setTyping(conversationId, false).catch(() => { });
|
|
293
366
|
}
|
|
367
|
+
function refreshVisibleWorkSignal(session) {
|
|
368
|
+
if (!session.running || session.closed)
|
|
369
|
+
return;
|
|
370
|
+
if (session.turnState !== 'thinking' && session.turnState !== 'tool')
|
|
371
|
+
return;
|
|
372
|
+
client.setTyping(session.conversationId, true, 'thinking').catch(() => { });
|
|
373
|
+
}
|
|
374
|
+
function startVisibleWorkSignal(session) {
|
|
375
|
+
refreshVisibleWorkSignal(session);
|
|
376
|
+
if (session.typingKeepaliveTimer)
|
|
377
|
+
return;
|
|
378
|
+
session.typingKeepaliveTimer = setInterval(() => {
|
|
379
|
+
refreshVisibleWorkSignal(session);
|
|
380
|
+
}, 3500);
|
|
381
|
+
}
|
|
382
|
+
function stopVisibleWorkSignal(session) {
|
|
383
|
+
if (session.typingKeepaliveTimer) {
|
|
384
|
+
clearInterval(session.typingKeepaliveTimer);
|
|
385
|
+
session.typingKeepaliveTimer = null;
|
|
386
|
+
}
|
|
387
|
+
}
|
|
294
388
|
function closeSession(conversationId) {
|
|
295
389
|
const session = sessions.get(conversationId);
|
|
296
390
|
if (!session)
|
|
297
391
|
return;
|
|
298
392
|
session.closed = true;
|
|
393
|
+
stopVisibleWorkSignal(session);
|
|
299
394
|
releaseConversationEnvironment(session.environment);
|
|
300
395
|
clearStreaming(conversationId);
|
|
301
396
|
clearSessionState(conversationId, agentId).catch(() => { });
|
|
@@ -342,7 +437,7 @@ export async function main() {
|
|
|
342
437
|
try {
|
|
343
438
|
const sessionCwd = environment.cwd;
|
|
344
439
|
const sessionModel = config?.model ?? (typeof args.model === 'string' ? args.model : undefined);
|
|
345
|
-
const storedThreadId = loadStoredThreadId(agentId, conversationId,
|
|
440
|
+
const storedThreadId = loadStoredThreadId(runtimeId, agentId, conversationId, environment.baseCwd, environment.mode);
|
|
346
441
|
if (config?.permissionMode
|
|
347
442
|
&& !codexPermissionEnvelope.availablePermissionModes.some((option) => option.value === config.permissionMode)) {
|
|
348
443
|
throw new ExecutionEnvironmentError(`Permission mode "${config.permissionMode}" is not supported by this Codex host.`, 'This Canon host was started with stricter approval settings. Choose one of the advertised permission modes or restart the host with more permissive flags.');
|
|
@@ -383,6 +478,7 @@ export async function main() {
|
|
|
383
478
|
currentTurnOpenedAt: null,
|
|
384
479
|
lastAcceptedIntent: null,
|
|
385
480
|
lastActivity: Date.now(),
|
|
481
|
+
typingKeepaliveTimer: null,
|
|
386
482
|
closed: false,
|
|
387
483
|
};
|
|
388
484
|
sessions.set(conversationId, session);
|
|
@@ -462,7 +558,12 @@ export async function main() {
|
|
|
462
558
|
const message = error instanceof Error ? error.message : String(error);
|
|
463
559
|
const userMessage = error instanceof ExecutionEnvironmentError ? error.userMessage : message;
|
|
464
560
|
console.error(`[canon-codex] [${input.conversationId.slice(0, 8)}] Failed to create session: ${message}`);
|
|
465
|
-
await client.sendMessage(input.conversationId, `I couldn't start a coding session for this workspace: ${userMessage}
|
|
561
|
+
await client.sendMessage(input.conversationId, `I couldn't start a coding session for this workspace: ${userMessage}`, {
|
|
562
|
+
metadata: {
|
|
563
|
+
turnSemantics: 'turn_complete',
|
|
564
|
+
turnComplete: true,
|
|
565
|
+
},
|
|
566
|
+
}).catch(() => { });
|
|
466
567
|
return;
|
|
467
568
|
}
|
|
468
569
|
const turnMetadata = normalizeTurnMetadata(input.message.metadata);
|
|
@@ -503,7 +604,7 @@ export async function main() {
|
|
|
503
604
|
await markQueuedMessageAccepted(session.conversationId, nextTurn.sourceMessageId, nextTurn.markAccepted);
|
|
504
605
|
writeState(session);
|
|
505
606
|
writeTurn(session);
|
|
506
|
-
|
|
607
|
+
startVisibleWorkSignal(session);
|
|
507
608
|
rtdbWrite(`/streaming/${session.conversationId}/${agentId}`, {
|
|
508
609
|
text: 'Thinking…',
|
|
509
610
|
status: 'thinking',
|
|
@@ -514,13 +615,15 @@ export async function main() {
|
|
|
514
615
|
const result = await session.adapter.runTurn(nextTurn.prompt, (event) => {
|
|
515
616
|
session.lastActivity = Date.now();
|
|
516
617
|
if (event.type === 'thread.started') {
|
|
517
|
-
saveStoredThreadId(agentId, session.conversationId, session.
|
|
618
|
+
saveStoredThreadId(runtimeId, agentId, session.conversationId, session.environment.baseCwd, event.threadId, session.environment.mode);
|
|
518
619
|
console.error(`[canon-codex] [${session.conversationId.slice(0, 8)}] Thread ${event.threadId}`);
|
|
519
620
|
return;
|
|
520
621
|
}
|
|
521
622
|
if (event.type === 'message') {
|
|
522
623
|
session.turnState = 'streaming';
|
|
523
624
|
writeTurn(session);
|
|
625
|
+
stopVisibleWorkSignal(session);
|
|
626
|
+
client.setTyping(session.conversationId, false).catch(() => { });
|
|
524
627
|
rtdbWrite(`/streaming/${session.conversationId}/${agentId}`, {
|
|
525
628
|
text: event.text,
|
|
526
629
|
status: 'streaming',
|
|
@@ -531,7 +634,7 @@ export async function main() {
|
|
|
531
634
|
if (event.type === 'command.started') {
|
|
532
635
|
session.turnState = 'tool';
|
|
533
636
|
writeTurn(session);
|
|
534
|
-
|
|
637
|
+
startVisibleWorkSignal(session);
|
|
535
638
|
rtdbWrite(`/streaming/${session.conversationId}/${agentId}`, {
|
|
536
639
|
text: summarizeCommand(event.command),
|
|
537
640
|
status: 'tool',
|
|
@@ -546,7 +649,7 @@ export async function main() {
|
|
|
546
649
|
console.error(`[canon-codex] [${session.conversationId.slice(0, 8)}] ${line}`);
|
|
547
650
|
}, turnImagePaths);
|
|
548
651
|
if (result.threadId) {
|
|
549
|
-
saveStoredThreadId(agentId, session.conversationId, session.
|
|
652
|
+
saveStoredThreadId(runtimeId, agentId, session.conversationId, session.environment.baseCwd, result.threadId, session.environment.mode);
|
|
550
653
|
}
|
|
551
654
|
if (!result.interrupted && result.finalMessage) {
|
|
552
655
|
await client.sendMessage(session.conversationId, result.finalMessage, {
|
|
@@ -583,6 +686,7 @@ export async function main() {
|
|
|
583
686
|
else if (result.interrupted) {
|
|
584
687
|
session.turnState = 'interrupted';
|
|
585
688
|
writeTurn(session);
|
|
689
|
+
stopVisibleWorkSignal(session);
|
|
586
690
|
clearStreaming(session.conversationId);
|
|
587
691
|
client.setTyping(session.conversationId, false).catch(() => { });
|
|
588
692
|
console.error(`[canon-codex] [${session.conversationId.slice(0, 8)}] Turn interrupted`);
|
|
@@ -601,10 +705,13 @@ export async function main() {
|
|
|
601
705
|
},
|
|
602
706
|
}).catch(() => { });
|
|
603
707
|
await handoffFinalMessage(session.conversationId);
|
|
604
|
-
|
|
708
|
+
if (error instanceof Error && /invalid|not found|unknown thread/i.test(error.message)) {
|
|
709
|
+
clearStoredThreadId(runtimeId, agentId, session.conversationId, session.environment.baseCwd, session.environment.mode);
|
|
710
|
+
}
|
|
605
711
|
console.error(`[canon-codex] [${session.conversationId.slice(0, 8)}] Turn failed:`, error);
|
|
606
712
|
}
|
|
607
713
|
finally {
|
|
714
|
+
stopVisibleWorkSignal(session);
|
|
608
715
|
session.running = false;
|
|
609
716
|
session.state.state = 'idle';
|
|
610
717
|
session.turnState = 'idle';
|
|
@@ -637,12 +744,19 @@ export async function main() {
|
|
|
637
744
|
runtimeDescriptor: buildCodexRuntimeDescriptor({
|
|
638
745
|
models: [],
|
|
639
746
|
workspaces: buildPublicWorkspaceOptions(workspaceOptions),
|
|
747
|
+
workspaceRoots: workspaceRootMetadata,
|
|
640
748
|
executionModes: hostAvailableExecutionModes,
|
|
641
749
|
permissionModes: [...codexPermissionEnvelope.availablePermissionModes],
|
|
642
750
|
defaultPermissionMode: codexPermissionEnvelope.defaultPermissionMode,
|
|
643
751
|
}),
|
|
644
752
|
};
|
|
645
753
|
const publishRuntimeHeartbeat = async () => {
|
|
754
|
+
heartbeatLocalRuntimeEntry(runtimeId, {
|
|
755
|
+
agentId,
|
|
756
|
+
agentName: profileAgentName,
|
|
757
|
+
cwd: workingDir,
|
|
758
|
+
baseCwd: workingDir,
|
|
759
|
+
});
|
|
646
760
|
if (!streamConnected)
|
|
647
761
|
return;
|
|
648
762
|
await refreshKnownConversationIds().catch((error) => {
|
|
@@ -683,6 +797,7 @@ export async function main() {
|
|
|
683
797
|
descriptor: runtimeDescriptor.runtimeDescriptor ?? buildCodexRuntimeDescriptor({
|
|
684
798
|
models: runtimeDescriptor.availableModels ?? [],
|
|
685
799
|
workspaces: buildPublicWorkspaceOptions(workspaceOptions),
|
|
800
|
+
workspaceRoots: workspaceRootMetadata,
|
|
686
801
|
executionModes: hostAvailableExecutionModes,
|
|
687
802
|
permissionModes: [...codexPermissionEnvelope.availablePermissionModes],
|
|
688
803
|
defaultPermissionMode: codexPermissionEnvelope.defaultPermissionMode,
|
|
@@ -699,10 +814,18 @@ export async function main() {
|
|
|
699
814
|
label: 'Live output',
|
|
700
815
|
value: 'Thinking, tools, and completed-message previews',
|
|
701
816
|
},
|
|
817
|
+
{
|
|
818
|
+
id: 'nativeActions',
|
|
819
|
+
label: 'Native actions',
|
|
820
|
+
value: 'Limited until app-server transport',
|
|
821
|
+
tone: 'warning',
|
|
822
|
+
},
|
|
702
823
|
],
|
|
703
824
|
execution: {
|
|
704
825
|
resolvedWorkspaceLabel: workspace?.label ?? workspaceId ?? null,
|
|
705
826
|
resolvedCwd: session?.cwd ?? workspace?.cwd ?? workingDir,
|
|
827
|
+
workspaceRootId: workspace?.workspaceRootId ?? null,
|
|
828
|
+
workspaceRelativePath: workspace?.workspaceRelativePath ?? null,
|
|
706
829
|
executionMode: session?.environment.mode ?? null,
|
|
707
830
|
executionBranch: session?.environment.branch ?? null,
|
|
708
831
|
worktreePath: session?.environment.worktreePath ?? null,
|
|
@@ -710,6 +833,7 @@ export async function main() {
|
|
|
710
833
|
},
|
|
711
834
|
notes: [
|
|
712
835
|
'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.',
|
|
836
|
+
'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.',
|
|
713
837
|
],
|
|
714
838
|
};
|
|
715
839
|
await writeRuntimeInfo(conversationId, agentId, payload);
|
|
@@ -733,6 +857,13 @@ export async function main() {
|
|
|
733
857
|
behavior: payload.behavior,
|
|
734
858
|
workSessions: payload.workSessions,
|
|
735
859
|
});
|
|
860
|
+
if (message.id) {
|
|
861
|
+
saveRuntimeSessionState(runtimeId, {
|
|
862
|
+
conversationId: payload.conversationId,
|
|
863
|
+
baseCwd: workingDir,
|
|
864
|
+
lastInboundMessageId: message.id,
|
|
865
|
+
});
|
|
866
|
+
}
|
|
736
867
|
},
|
|
737
868
|
onConnected: () => {
|
|
738
869
|
streamConnected = true;
|
|
@@ -760,6 +891,7 @@ export async function main() {
|
|
|
760
891
|
runtimeDescriptor: buildCodexRuntimeDescriptor({
|
|
761
892
|
models: [],
|
|
762
893
|
workspaces: buildPublicWorkspaceOptions(workspaceOptions),
|
|
894
|
+
workspaceRoots: workspaceRootMetadata,
|
|
763
895
|
executionModes: hostAvailableExecutionModes,
|
|
764
896
|
permissionModes: [...codexPermissionEnvelope.availablePermissionModes],
|
|
765
897
|
defaultPermissionMode: codexPermissionEnvelope.defaultPermissionMode,
|
|
@@ -778,6 +910,7 @@ export async function main() {
|
|
|
778
910
|
runtimeDescriptor: buildCodexRuntimeDescriptor({
|
|
779
911
|
models: [],
|
|
780
912
|
workspaces: buildPublicWorkspaceOptions(workspaceOptions),
|
|
913
|
+
workspaceRoots: workspaceRootMetadata,
|
|
781
914
|
executionModes: hostAvailableExecutionModes,
|
|
782
915
|
permissionModes: [...codexPermissionEnvelope.availablePermissionModes],
|
|
783
916
|
defaultPermissionMode: codexPermissionEnvelope.defaultPermissionMode,
|
|
@@ -795,34 +928,51 @@ export async function main() {
|
|
|
795
928
|
clearTurnState(conversation.id, agentId).catch(() => { });
|
|
796
929
|
}
|
|
797
930
|
for (const conversation of conversations) {
|
|
798
|
-
|
|
799
|
-
continue;
|
|
800
|
-
const latestPage = await client.getMessagesPage(conversation.id, 1);
|
|
801
|
-
const latestMessage = latestPage.messages[0];
|
|
802
|
-
if (!latestMessage || latestMessage.senderId === agentId)
|
|
803
|
-
continue;
|
|
804
|
-
const senderTurnState = latestMessage.senderType === 'ai_agent'
|
|
805
|
-
? await loadSenderRuntimeState(conversation.id, latestMessage.senderId)
|
|
806
|
-
: null;
|
|
807
|
-
const triggerDecision = shouldTriggerAgentTurn({
|
|
808
|
-
senderType: latestMessage.senderType ?? 'human',
|
|
809
|
-
metadata: latestMessage.metadata,
|
|
810
|
-
senderTurnState,
|
|
811
|
-
});
|
|
812
|
-
if (!triggerDecision.allow) {
|
|
813
|
-
console.error(`[canon-codex] [${conversation.id.slice(0, 8)}] Skipping startup recovery for suppressed message (${triggerDecision.reason})`);
|
|
814
|
-
continue;
|
|
815
|
-
}
|
|
816
|
-
console.error(`[canon-codex] [${conversation.id.slice(0, 8)}] Recovering latest inbound message on startup`);
|
|
817
|
-
await enqueueInboundMessage({
|
|
931
|
+
const cursor = loadRuntimeSessionState(runtimeId, {
|
|
818
932
|
conversationId: conversation.id,
|
|
819
|
-
|
|
820
|
-
|
|
821
|
-
|
|
822
|
-
|
|
823
|
-
|
|
824
|
-
|
|
825
|
-
|
|
933
|
+
baseCwd: workingDir,
|
|
934
|
+
})?.lastInboundMessageId;
|
|
935
|
+
const latestPage = await client.getMessagesPage(conversation.id, 25);
|
|
936
|
+
const inboundMessages = latestPage.messages
|
|
937
|
+
.filter((message) => message.senderId !== agentId)
|
|
938
|
+
.sort((a, b) => String(a.createdAt ?? '').localeCompare(String(b.createdAt ?? '')));
|
|
939
|
+
const cursorIndex = cursor
|
|
940
|
+
? inboundMessages.findIndex((message) => message.id === cursor)
|
|
941
|
+
: -1;
|
|
942
|
+
const messagesToRecover = cursorIndex >= 0
|
|
943
|
+
? inboundMessages.slice(cursorIndex + 1)
|
|
944
|
+
: inboundMessages.slice(-1);
|
|
945
|
+
for (const latestMessage of messagesToRecover) {
|
|
946
|
+
const senderTurnState = latestMessage.senderType === 'ai_agent'
|
|
947
|
+
? await loadSenderRuntimeState(conversation.id, latestMessage.senderId)
|
|
948
|
+
: null;
|
|
949
|
+
const triggerDecision = shouldTriggerAgentTurn({
|
|
950
|
+
senderType: latestMessage.senderType ?? 'human',
|
|
951
|
+
metadata: latestMessage.metadata,
|
|
952
|
+
senderTurnState,
|
|
953
|
+
});
|
|
954
|
+
if (!triggerDecision.allow) {
|
|
955
|
+
console.error(`[canon-codex] [${conversation.id.slice(0, 8)}] Skipping startup recovery for suppressed message (${triggerDecision.reason})`);
|
|
956
|
+
continue;
|
|
957
|
+
}
|
|
958
|
+
console.error(`[canon-codex] [${conversation.id.slice(0, 8)}] Recovering inbound message on startup`);
|
|
959
|
+
await enqueueInboundMessage({
|
|
960
|
+
conversationId: conversation.id,
|
|
961
|
+
message: latestMessage,
|
|
962
|
+
senderName: latestMessage.senderId,
|
|
963
|
+
isOwner: ownerId != null && latestMessage.senderId === ownerId,
|
|
964
|
+
behavior: latestPage.behavior,
|
|
965
|
+
workSessions: latestPage.workSessions,
|
|
966
|
+
hydratedPage: latestPage,
|
|
967
|
+
});
|
|
968
|
+
if (latestMessage.id) {
|
|
969
|
+
saveRuntimeSessionState(runtimeId, {
|
|
970
|
+
conversationId: conversation.id,
|
|
971
|
+
baseCwd: workingDir,
|
|
972
|
+
lastInboundMessageId: latestMessage.id,
|
|
973
|
+
});
|
|
974
|
+
}
|
|
975
|
+
}
|
|
826
976
|
}
|
|
827
977
|
}
|
|
828
978
|
catch (error) {
|
|
@@ -921,20 +1071,18 @@ export async function main() {
|
|
|
921
1071
|
await session.adapter.interrupt().catch(() => { });
|
|
922
1072
|
closeSession(session.conversationId);
|
|
923
1073
|
}
|
|
924
|
-
|
|
925
|
-
|
|
926
|
-
releaseLock(activeProfile);
|
|
1074
|
+
markLocalRuntimeStopped(runtimeId);
|
|
1075
|
+
(lockHandle ?? getActiveProfileLock())?.release();
|
|
927
1076
|
process.exit(0);
|
|
928
1077
|
};
|
|
929
1078
|
process.on('SIGINT', shutdown);
|
|
930
1079
|
process.on('SIGTERM', shutdown);
|
|
1080
|
+
process.on('SIGHUP', shutdown);
|
|
931
1081
|
console.error('[canon-codex] Ready — sessions created on demand');
|
|
932
1082
|
await new Promise(() => { });
|
|
933
1083
|
}
|
|
934
1084
|
runCli(import.meta.url, main, (error) => {
|
|
935
1085
|
console.error('[canon-codex] Fatal error:', error);
|
|
936
|
-
|
|
937
|
-
if (activeProfile)
|
|
938
|
-
releaseLock(activeProfile);
|
|
1086
|
+
getActiveProfileLock()?.release();
|
|
939
1087
|
process.exit(1);
|
|
940
1088
|
});
|
package/dist/register.js
CHANGED
|
@@ -1,13 +1,9 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import { setDefaultResultOrder } from 'node:dns';
|
|
3
|
-
import {
|
|
4
|
-
import { homedir } from 'node:os';
|
|
5
|
-
import { join } from 'node:path';
|
|
3
|
+
import { readFileSync } from 'node:fs';
|
|
6
4
|
import { parseArgs } from 'node:util';
|
|
7
|
-
import { registerAndWaitForApproval } from '@canonmsg/core';
|
|
5
|
+
import { ackRegistrationApproval, clearPendingRegistration, getOrCreatePendingRegistration, registerAndWaitForApproval, updatePendingRegistration, upsertAgentProfile, AGENTS_PATH, } from '@canonmsg/core';
|
|
8
6
|
import { runCli } from './cli-entry.js';
|
|
9
|
-
const CANON_DIR = join(homedir(), '.canon');
|
|
10
|
-
const AGENTS_PATH = join(CANON_DIR, 'agents.json');
|
|
11
7
|
export async function main() {
|
|
12
8
|
setDefaultResultOrder('ipv4first');
|
|
13
9
|
const { values } = parseArgs({
|
|
@@ -34,6 +30,7 @@ export async function main() {
|
|
|
34
30
|
// No existing profile state.
|
|
35
31
|
}
|
|
36
32
|
console.log(`Registering Codex agent "${values.name}" (profile: ${profileName})...`);
|
|
33
|
+
const pending = getOrCreatePendingRegistration(profileName, 'codex');
|
|
37
34
|
const result = await registerAndWaitForApproval({
|
|
38
35
|
name: values.name,
|
|
39
36
|
description: values.description,
|
|
@@ -42,8 +39,14 @@ export async function main() {
|
|
|
42
39
|
clientType: 'codex',
|
|
43
40
|
baseUrl: values['base-url'],
|
|
44
41
|
requestedAgentId: existingAgentId,
|
|
42
|
+
localRegistrationId: pending.localRegistrationId,
|
|
45
43
|
}, {
|
|
46
|
-
onSubmitted: (requestId) => {
|
|
44
|
+
onSubmitted: (requestId, pollToken) => {
|
|
45
|
+
updatePendingRegistration(profileName, {
|
|
46
|
+
requestId,
|
|
47
|
+
pollToken,
|
|
48
|
+
clientType: 'codex',
|
|
49
|
+
});
|
|
47
50
|
console.log(`Registration submitted (request ID: ${requestId}).`);
|
|
48
51
|
console.log('Waiting for approval in Canon app...');
|
|
49
52
|
},
|
|
@@ -54,24 +57,26 @@ export async function main() {
|
|
|
54
57
|
console.log('');
|
|
55
58
|
switch (result.status) {
|
|
56
59
|
case 'approved': {
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
profiles = JSON.parse(readFileSync(AGENTS_PATH, 'utf-8'));
|
|
60
|
+
if (!result.apiKey || !result.agentId || !result.agentName) {
|
|
61
|
+
console.error('Approval completed but Canon did not return a usable API key. Run this command again to resume key pickup.');
|
|
62
|
+
process.exit(1);
|
|
61
63
|
}
|
|
62
|
-
|
|
63
|
-
// File does not exist yet.
|
|
64
|
-
}
|
|
65
|
-
profiles[profileName] = {
|
|
64
|
+
upsertAgentProfile(profileName, {
|
|
66
65
|
apiKey: result.apiKey,
|
|
67
66
|
agentId: result.agentId,
|
|
68
67
|
agentName: result.agentName,
|
|
69
68
|
registeredAt: new Date().toISOString(),
|
|
70
|
-
|
|
71
|
-
|
|
69
|
+
clientType: 'codex',
|
|
70
|
+
...(typeof values['base-url'] === 'string' ? { baseUrl: values['base-url'] } : {}),
|
|
71
|
+
});
|
|
72
|
+
if (result.requestId) {
|
|
73
|
+
await ackRegistrationApproval(values['base-url'], result.requestId, result.pollToken);
|
|
74
|
+
}
|
|
75
|
+
clearPendingRegistration(profileName);
|
|
72
76
|
console.log(`Approved! Agent: ${result.agentName} (${result.agentId})`);
|
|
73
77
|
console.log(`Saved as profile "${profileName}" in ~/.canon/agents.json`);
|
|
74
|
-
console.log('Start it with: canon-codex --cwd /path/to/project');
|
|
78
|
+
console.log('Start it with: CANON_AGENT=' + profileName + ' canon-codex --cwd /path/to/project');
|
|
79
|
+
console.log('Keep that terminal open. Closing it takes this local agent offline.');
|
|
75
80
|
break;
|
|
76
81
|
}
|
|
77
82
|
case 'rejected':
|
package/dist/session-store.d.ts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
-
|
|
2
|
-
export declare function
|
|
3
|
-
export declare function
|
|
1
|
+
import { type ExecutionEnvironmentMode } from '@canonmsg/core';
|
|
2
|
+
export declare function loadStoredThreadId(runtimeId: string | null, agentId: string, conversationId: string, baseCwd: string, executionMode?: ExecutionEnvironmentMode): string | null;
|
|
3
|
+
export declare function saveStoredThreadId(runtimeId: string | null, agentId: string, conversationId: string, baseCwd: string, threadId: string, executionMode?: ExecutionEnvironmentMode): void;
|
|
4
|
+
export declare function clearStoredThreadId(runtimeId: string | null, agentId: string, conversationId: string, baseCwd?: string, executionMode?: ExecutionEnvironmentMode): void;
|
package/dist/session-store.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { mkdirSync, readFileSync, writeFileSync } from 'node:fs';
|
|
2
2
|
import { join } from 'node:path';
|
|
3
|
-
import { CANON_DIR } from '@canonmsg/core';
|
|
3
|
+
import { CANON_DIR, clearRuntimeSessionState, loadRuntimeSessionState, saveRuntimeSessionState, } from '@canonmsg/core';
|
|
4
4
|
const STORE_PATH = join(CANON_DIR, 'codex-sessions.json');
|
|
5
5
|
function loadStore() {
|
|
6
6
|
try {
|
|
@@ -14,24 +14,50 @@ function saveStore(store) {
|
|
|
14
14
|
mkdirSync(CANON_DIR, { recursive: true });
|
|
15
15
|
writeFileSync(STORE_PATH, JSON.stringify(store, null, 2));
|
|
16
16
|
}
|
|
17
|
-
export function loadStoredThreadId(agentId, conversationId,
|
|
17
|
+
export function loadStoredThreadId(runtimeId, agentId, conversationId, baseCwd, executionMode) {
|
|
18
|
+
if (runtimeId) {
|
|
19
|
+
const state = loadRuntimeSessionState(runtimeId, {
|
|
20
|
+
conversationId,
|
|
21
|
+
baseCwd,
|
|
22
|
+
executionMode,
|
|
23
|
+
});
|
|
24
|
+
if (state?.threadId)
|
|
25
|
+
return state.threadId;
|
|
26
|
+
}
|
|
18
27
|
const store = loadStore();
|
|
19
28
|
const record = store.agents[agentId]?.[conversationId];
|
|
20
|
-
if (!record || record.cwd !==
|
|
29
|
+
if (!record || record.cwd !== baseCwd)
|
|
21
30
|
return null;
|
|
22
31
|
return record.threadId;
|
|
23
32
|
}
|
|
24
|
-
export function saveStoredThreadId(agentId, conversationId,
|
|
33
|
+
export function saveStoredThreadId(runtimeId, agentId, conversationId, baseCwd, threadId, executionMode) {
|
|
34
|
+
if (runtimeId) {
|
|
35
|
+
saveRuntimeSessionState(runtimeId, {
|
|
36
|
+
conversationId,
|
|
37
|
+
baseCwd,
|
|
38
|
+
executionMode,
|
|
39
|
+
threadId,
|
|
40
|
+
});
|
|
41
|
+
return;
|
|
42
|
+
}
|
|
25
43
|
const store = loadStore();
|
|
26
44
|
store.agents[agentId] ??= {};
|
|
27
45
|
store.agents[agentId][conversationId] = {
|
|
28
46
|
threadId,
|
|
29
|
-
cwd,
|
|
47
|
+
cwd: baseCwd,
|
|
30
48
|
updatedAt: new Date().toISOString(),
|
|
31
49
|
};
|
|
32
50
|
saveStore(store);
|
|
33
51
|
}
|
|
34
|
-
export function clearStoredThreadId(agentId, conversationId) {
|
|
52
|
+
export function clearStoredThreadId(runtimeId, agentId, conversationId, baseCwd, executionMode) {
|
|
53
|
+
if (runtimeId) {
|
|
54
|
+
clearRuntimeSessionState(runtimeId, {
|
|
55
|
+
conversationId,
|
|
56
|
+
baseCwd,
|
|
57
|
+
executionMode,
|
|
58
|
+
});
|
|
59
|
+
return;
|
|
60
|
+
}
|
|
35
61
|
const store = loadStore();
|
|
36
62
|
if (!store.agents[agentId]?.[conversationId])
|
|
37
63
|
return;
|
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.1",
|
|
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.10.1",
|
|
33
|
+
"@canonmsg/core": "^0.13.0"
|
|
34
34
|
},
|
|
35
35
|
"engines": {
|
|
36
36
|
"node": ">=18.0.0"
|