@canonmsg/codex-plugin 0.13.2 → 0.14.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 +3 -1
- package/dist/adapter.d.ts +2 -0
- package/dist/adapter.js +6 -0
- package/dist/app-server-adapter.js +31 -6
- package/dist/host.js +38 -14
- package/dist/turn-activity.d.ts +21 -0
- package/dist/turn-activity.js +47 -0
- package/package.json +4 -4
package/README.md
CHANGED
|
@@ -80,11 +80,13 @@ Worktree mode is project isolation, not an operating-system sandbox. The Codex C
|
|
|
80
80
|
Useful flags:
|
|
81
81
|
|
|
82
82
|
```bash
|
|
83
|
-
canon-codex --cwd /path/to/project --model gpt-5.
|
|
83
|
+
canon-codex --cwd /path/to/project --model gpt-5.5 --full-auto
|
|
84
84
|
```
|
|
85
85
|
|
|
86
86
|
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.
|
|
87
87
|
|
|
88
|
+
`gpt-5.5` requires a recent local Codex CLI. Older CLIs fail early with an upgrade message; upgrade Codex or choose a model supported by your installed CLI.
|
|
89
|
+
|
|
88
90
|
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`.
|
|
89
91
|
|
|
90
92
|
Do not start Canon with `--sandbox danger-full-access` as an unlabeled default. Use `--dangerously-bypass-approvals-and-sandbox` only when you intentionally want Canon to advertise the owner-only Bypass policy.
|
package/dist/adapter.d.ts
CHANGED
|
@@ -17,11 +17,13 @@ export type CodexEvent = {
|
|
|
17
17
|
} | {
|
|
18
18
|
type: 'command.started';
|
|
19
19
|
command: string;
|
|
20
|
+
itemId?: string;
|
|
20
21
|
} | {
|
|
21
22
|
type: 'command.completed';
|
|
22
23
|
command: string;
|
|
23
24
|
output: string;
|
|
24
25
|
exitCode: number | null;
|
|
26
|
+
itemId?: string;
|
|
25
27
|
} | {
|
|
26
28
|
type: 'turn.completed';
|
|
27
29
|
usage?: {
|
package/dist/adapter.js
CHANGED
|
@@ -83,6 +83,9 @@ export class CodexConversationAdapter {
|
|
|
83
83
|
onEvent({
|
|
84
84
|
type: 'command.started',
|
|
85
85
|
command: String(event.item.command ?? ''),
|
|
86
|
+
...(typeof event.item.id === 'string' && event.item.id.trim()
|
|
87
|
+
? { itemId: event.item.id.trim() }
|
|
88
|
+
: {}),
|
|
86
89
|
});
|
|
87
90
|
}
|
|
88
91
|
break;
|
|
@@ -99,6 +102,9 @@ export class CodexConversationAdapter {
|
|
|
99
102
|
command: String(event.item.command ?? ''),
|
|
100
103
|
output: String(event.item.aggregated_output ?? ''),
|
|
101
104
|
exitCode: typeof event.item.exit_code === 'number' ? event.item.exit_code : null,
|
|
105
|
+
...(typeof event.item.id === 'string' && event.item.id.trim()
|
|
106
|
+
? { itemId: event.item.id.trim() }
|
|
107
|
+
: {}),
|
|
102
108
|
});
|
|
103
109
|
}
|
|
104
110
|
break;
|
|
@@ -311,10 +311,10 @@ export class CodexAppServerAdapter {
|
|
|
311
311
|
}
|
|
312
312
|
if (method === 'item/agentMessage/delta') {
|
|
313
313
|
const itemId = readString(params, 'itemId') ?? 'agent-message';
|
|
314
|
-
const delta =
|
|
314
|
+
const delta = readRawString(params, 'delta') ?? '';
|
|
315
315
|
const next = `${this.messageTextByItem.get(itemId) ?? ''}${delta}`;
|
|
316
316
|
this.messageTextByItem.set(itemId, next);
|
|
317
|
-
this.currentFinalMessage = next.trim()
|
|
317
|
+
this.currentFinalMessage = next.trim() ? next : this.currentFinalMessage;
|
|
318
318
|
if (next.trim())
|
|
319
319
|
this.currentOnEvent?.({ type: 'message', text: next });
|
|
320
320
|
return;
|
|
@@ -327,9 +327,9 @@ export class CodexAppServerAdapter {
|
|
|
327
327
|
return;
|
|
328
328
|
}
|
|
329
329
|
if (method === 'item/plan/delta') {
|
|
330
|
-
const delta =
|
|
330
|
+
const delta = readRawString(params, 'delta') ?? '';
|
|
331
331
|
this.planText = `${this.planText}${delta}`;
|
|
332
|
-
this.currentFinalMessage = this.planText.trim()
|
|
332
|
+
this.currentFinalMessage = this.planText.trim() ? this.planText : this.currentFinalMessage;
|
|
333
333
|
if (this.planText.trim())
|
|
334
334
|
this.currentOnEvent?.({ type: 'plan.updated', text: this.planText });
|
|
335
335
|
return;
|
|
@@ -337,8 +337,14 @@ export class CodexAppServerAdapter {
|
|
|
337
337
|
if (method === 'item/started') {
|
|
338
338
|
const item = params.item;
|
|
339
339
|
const summary = summarizeItem(item);
|
|
340
|
-
if (summary)
|
|
341
|
-
|
|
340
|
+
if (summary) {
|
|
341
|
+
const itemId = readString(item, 'id');
|
|
342
|
+
this.currentOnEvent?.({
|
|
343
|
+
type: 'command.started',
|
|
344
|
+
command: summary,
|
|
345
|
+
...(itemId ? { itemId } : {}),
|
|
346
|
+
});
|
|
347
|
+
}
|
|
342
348
|
return;
|
|
343
349
|
}
|
|
344
350
|
if (method === 'item/completed') {
|
|
@@ -351,13 +357,28 @@ export class CodexAppServerAdapter {
|
|
|
351
357
|
}
|
|
352
358
|
}
|
|
353
359
|
else if (item?.type === 'commandExecution') {
|
|
360
|
+
const itemId = readString(item, 'id');
|
|
354
361
|
this.currentOnEvent?.({
|
|
355
362
|
type: 'command.completed',
|
|
356
363
|
command: readString(item, 'command') ?? 'Command',
|
|
357
364
|
output: readString(item, 'aggregatedOutput') ?? '',
|
|
358
365
|
exitCode: typeof item.exitCode === 'number' ? item.exitCode : null,
|
|
366
|
+
...(itemId ? { itemId } : {}),
|
|
359
367
|
});
|
|
360
368
|
}
|
|
369
|
+
else {
|
|
370
|
+
const summary = summarizeItem(item);
|
|
371
|
+
if (summary) {
|
|
372
|
+
const itemId = readString(item, 'id');
|
|
373
|
+
this.currentOnEvent?.({
|
|
374
|
+
type: 'command.completed',
|
|
375
|
+
command: summary,
|
|
376
|
+
output: '',
|
|
377
|
+
exitCode: null,
|
|
378
|
+
...(itemId ? { itemId } : {}),
|
|
379
|
+
});
|
|
380
|
+
}
|
|
381
|
+
}
|
|
361
382
|
return;
|
|
362
383
|
}
|
|
363
384
|
if (method === 'turn/completed') {
|
|
@@ -432,6 +453,10 @@ function readString(record, key) {
|
|
|
432
453
|
const value = record?.[key];
|
|
433
454
|
return typeof value === 'string' && value.trim() ? value.trim() : undefined;
|
|
434
455
|
}
|
|
456
|
+
function readRawString(record, key) {
|
|
457
|
+
const value = record?.[key];
|
|
458
|
+
return typeof value === 'string' ? value : undefined;
|
|
459
|
+
}
|
|
435
460
|
function stringifyPreview(value) {
|
|
436
461
|
if (typeof value === 'string')
|
|
437
462
|
return value;
|
package/dist/host.js
CHANGED
|
@@ -5,8 +5,8 @@ import { spawnSync } from 'node:child_process';
|
|
|
5
5
|
import { dirname } from 'node:path';
|
|
6
6
|
import { parseArgs } from 'node:util';
|
|
7
7
|
import { getCodexImagePath, materializeMessageMedia, materializeReplyContextMedia, } from '@canonmsg/agent-sdk';
|
|
8
|
-
import { RUNTIME_NEW_SESSION_ACTION, RUNTIME_STOP_ACTION, RUNTIME_STOP_AND_DROP_ACTION, buildRuntimeCardOutcome, buildPlanApprovalRequest,
|
|
9
|
-
import {
|
|
8
|
+
import { RUNTIME_NEW_SESSION_ACTION, RUNTIME_STOP_ACTION, RUNTIME_STOP_AND_DROP_ACTION, buildRuntimeCardOutcome, buildPlanApprovalRequest, buildCanonTurnContextV2, buildConfiguredWorkspaceOptionsWithRoots, buildFirstPartyCodingRuntimeDescriptor, buildHydratedInboundContext, diffCanonMemberIds, buildPublicWorkspaceRoots, buildPublicWorkspaceOptions, buildRuntimePresentationPolicy, DEFAULT_FIRST_PARTY_RUNTIME_PRESENTATION, createConversationMetadataLoader, createRuntimeStatePublisher, createTypingStatusPublisher, EXECUTION_ENVIRONMENT_MODES, ExecutionEnvironmentError, CanonClient, CanonStream, DEFAULT_PARTICIPATION_HISTORY_FETCH_LIMIT, DEFAULT_RUNTIME_CAPABILITIES, FINAL_MESSAGE_HANDOFF_MS, getActiveProfileLock, initRTDBAuth, buildLocalRuntimeId, heartbeatLocalRuntimeEntry, loadRuntimeSessionState, markLocalRuntimeStopped, normalizeTurnMetadata, parseRuntimeCardV1, prepareConversationEnvironment, loadHostSessionConfig, releaseConversationEnvironment, resolveCanonAgent, rtdbRead, rtdbWrite, CanonApiError, sendMessageWithRetry, sendMessageWithRetryChunked, shouldTriggerAgentTurn, saveRuntimeSessionState, buildBoundedTurnTrail, publishHostAgentRuntime, publishHostSessionSnapshots, renderCanonHostInboundContent, renderCanonTurnBriefPrompt, resolveHostWorkspaceCwd, upsertLocalRuntimeEntry, } from '@canonmsg/core';
|
|
9
|
+
import { decideAutoReply, } from './inbound-policy.js';
|
|
10
10
|
import { CodexConversationAdapter, } from './adapter.js';
|
|
11
11
|
import { CodexAppServerAdapter } from './app-server-adapter.js';
|
|
12
12
|
import { mapCanonApprovalResultToCodexDecision, mapCodexAppServerApprovalRequest, } from './app-server-approval.js';
|
|
@@ -16,6 +16,7 @@ import { detectCodexCliVersion } from './codex-cli-version.js';
|
|
|
16
16
|
import { buildCodexModelGuardMessage, formatCodexTurnFailure, isRecoverableCodexThreadError, } from './error-format.js';
|
|
17
17
|
import { startCodexStreamInBackground } from './host-lifecycle.js';
|
|
18
18
|
import { runCli } from './cli-entry.js';
|
|
19
|
+
import { beginCommandBlock, claimCommandBlock, createCommandBlockTracker, } from './turn-activity.js';
|
|
19
20
|
const HELP = `canon-codex — run a local Codex agent host for Canon
|
|
20
21
|
|
|
21
22
|
USAGE
|
|
@@ -227,11 +228,17 @@ function resolveCodexEffectiveRuntimePolicy(input) {
|
|
|
227
228
|
};
|
|
228
229
|
}
|
|
229
230
|
function buildCanonPrompt(input) {
|
|
230
|
-
return
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
231
|
+
return renderCanonTurnBriefPrompt(buildCanonTurnContextV2({
|
|
232
|
+
content: input.content,
|
|
233
|
+
conversationId: input.conversationId,
|
|
234
|
+
participantContext: input.participantContext,
|
|
235
|
+
behavior: input.behavior,
|
|
236
|
+
selfContexts: input.selfContexts,
|
|
237
|
+
activeSelfContextId: input.activeSelfContextId,
|
|
238
|
+
provenance: input.provenance,
|
|
239
|
+
replyContext: input.replyContext,
|
|
240
|
+
message: input.message,
|
|
241
|
+
}));
|
|
235
242
|
}
|
|
236
243
|
function renderInboundContent(message, materialized) {
|
|
237
244
|
return renderCanonHostInboundContent(message, materialized);
|
|
@@ -844,6 +851,7 @@ export async function main() {
|
|
|
844
851
|
closed: false,
|
|
845
852
|
turnLiveText: '',
|
|
846
853
|
turnBlocks: [],
|
|
854
|
+
turnCommandBlocks: createCommandBlockTracker(),
|
|
847
855
|
};
|
|
848
856
|
sessions.set(conversationId, session);
|
|
849
857
|
await Promise.all([
|
|
@@ -1214,7 +1222,7 @@ export async function main() {
|
|
|
1214
1222
|
const userMessage = error instanceof ExecutionEnvironmentError ? error.userMessage : message;
|
|
1215
1223
|
console.error(`[canon-codex] [${input.conversationId.slice(0, 8)}] Failed to create session: ${message}`);
|
|
1216
1224
|
await markQueuedMessageAccepted(input.conversationId, input.message.id, shouldMarkAccepted);
|
|
1217
|
-
await
|
|
1225
|
+
await sendMessageWithRetryChunked(client, input.conversationId, `I couldn't start a coding session for this workspace: ${userMessage}`, {
|
|
1218
1226
|
messageId: `codex-start-failed-${input.message.id}`,
|
|
1219
1227
|
...(activeSelfContextId ? { selfContextId: activeSelfContextId } : {}),
|
|
1220
1228
|
metadata: {
|
|
@@ -1231,7 +1239,10 @@ export async function main() {
|
|
|
1231
1239
|
participantContext,
|
|
1232
1240
|
behavior,
|
|
1233
1241
|
selfContexts,
|
|
1242
|
+
activeSelfContextId,
|
|
1243
|
+
provenance: hydrated.provenance,
|
|
1234
1244
|
replyContext,
|
|
1245
|
+
message: input.message,
|
|
1235
1246
|
});
|
|
1236
1247
|
if (session.running && deliveryIntent === 'interrupt') {
|
|
1237
1248
|
enqueuePrompt(session, prompt, deliveryIntent, true, input.message.id, shouldMarkAccepted, imagePaths, mediaAddDirs, planCommand.planMode);
|
|
@@ -1255,6 +1266,7 @@ export async function main() {
|
|
|
1255
1266
|
session.currentTurnId = randomUUID();
|
|
1256
1267
|
session.turnLiveText = '';
|
|
1257
1268
|
session.turnBlocks = [];
|
|
1269
|
+
session.turnCommandBlocks = createCommandBlockTracker();
|
|
1258
1270
|
session.currentTurnOpenedAt = Date.now();
|
|
1259
1271
|
session.currentTurnUpdatedAt = session.currentTurnOpenedAt;
|
|
1260
1272
|
session.lastAcceptedIntent = nextTurn.intent;
|
|
@@ -1318,8 +1330,13 @@ export async function main() {
|
|
|
1318
1330
|
markTurnProgress(session);
|
|
1319
1331
|
writeTurn(session);
|
|
1320
1332
|
startVisibleWorkSignal(session);
|
|
1333
|
+
const blockId = beginCommandBlock(session.turnCommandBlocks, {
|
|
1334
|
+
turnId: session.currentTurnId,
|
|
1335
|
+
command: event.command,
|
|
1336
|
+
itemId: event.itemId,
|
|
1337
|
+
});
|
|
1321
1338
|
upsertTurnBlock(session, {
|
|
1322
|
-
id:
|
|
1339
|
+
id: blockId,
|
|
1323
1340
|
kind: 'tool',
|
|
1324
1341
|
status: 'running',
|
|
1325
1342
|
title: summarizeCommand(event.command),
|
|
@@ -1329,7 +1346,12 @@ export async function main() {
|
|
|
1329
1346
|
return;
|
|
1330
1347
|
}
|
|
1331
1348
|
if (event.type === 'command.completed') {
|
|
1332
|
-
|
|
1349
|
+
const blockId = claimCommandBlock(session.turnCommandBlocks, {
|
|
1350
|
+
turnId: session.currentTurnId,
|
|
1351
|
+
command: event.command,
|
|
1352
|
+
itemId: event.itemId,
|
|
1353
|
+
});
|
|
1354
|
+
completeTurnBlock(session, blockId, 'Command completed');
|
|
1333
1355
|
if (session.turnState === 'tool') {
|
|
1334
1356
|
session.turnState = 'thinking';
|
|
1335
1357
|
markTurnProgress(session);
|
|
@@ -1390,7 +1412,7 @@ export async function main() {
|
|
|
1390
1412
|
clearStoredThread();
|
|
1391
1413
|
}
|
|
1392
1414
|
const turnTrail = buildFinalTurnTrail(session);
|
|
1393
|
-
await
|
|
1415
|
+
await sendMessageWithRetryChunked(client, session.conversationId, result.finalMessage, {
|
|
1394
1416
|
messageId: buildCodexMessageId(session, 'final'),
|
|
1395
1417
|
...(session.activeSelfContextId
|
|
1396
1418
|
? { selfContextId: session.activeSelfContextId }
|
|
@@ -1413,7 +1435,7 @@ export async function main() {
|
|
|
1413
1435
|
console.error(`[canon-codex] [${session.conversationId.slice(0, 8)}] Turn exited ${result.exitCode}: ${result.errorText}`);
|
|
1414
1436
|
}
|
|
1415
1437
|
const turnTrail = buildFinalTurnTrail(session);
|
|
1416
|
-
await
|
|
1438
|
+
await sendMessageWithRetryChunked(client, session.conversationId, userVisibleError, {
|
|
1417
1439
|
messageId: buildCodexMessageId(session, 'error'),
|
|
1418
1440
|
...(session.activeSelfContextId
|
|
1419
1441
|
? { selfContextId: session.activeSelfContextId }
|
|
@@ -1442,10 +1464,12 @@ export async function main() {
|
|
|
1442
1464
|
catch (error) {
|
|
1443
1465
|
const message = error instanceof ExecutionEnvironmentError
|
|
1444
1466
|
? error.userMessage
|
|
1445
|
-
:
|
|
1467
|
+
: error instanceof CanonApiError
|
|
1468
|
+
? `The Codex host completed the turn, but Canon could not deliver the reply: ${error.message}`
|
|
1469
|
+
: `The Codex host failed during the turn: ${error instanceof Error ? error.message : String(error)}`;
|
|
1446
1470
|
session.state.lastError = message;
|
|
1447
1471
|
writeState(session);
|
|
1448
|
-
await
|
|
1472
|
+
await sendMessageWithRetryChunked(client, session.conversationId, message, {
|
|
1449
1473
|
messageId: buildCodexMessageId(session, 'failure'),
|
|
1450
1474
|
...(session.activeSelfContextId
|
|
1451
1475
|
? { selfContextId: session.activeSelfContextId }
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
interface RunningCommandBlock {
|
|
2
|
+
command: string;
|
|
3
|
+
blockId: string;
|
|
4
|
+
itemId?: string;
|
|
5
|
+
}
|
|
6
|
+
export interface CommandBlockTracker {
|
|
7
|
+
sequence: number;
|
|
8
|
+
running: RunningCommandBlock[];
|
|
9
|
+
}
|
|
10
|
+
export declare function createCommandBlockTracker(): CommandBlockTracker;
|
|
11
|
+
export declare function beginCommandBlock(tracker: CommandBlockTracker, input: {
|
|
12
|
+
turnId: string | null | undefined;
|
|
13
|
+
command: string;
|
|
14
|
+
itemId?: string;
|
|
15
|
+
}): string;
|
|
16
|
+
export declare function claimCommandBlock(tracker: CommandBlockTracker, input: {
|
|
17
|
+
turnId: string | null | undefined;
|
|
18
|
+
command: string;
|
|
19
|
+
itemId?: string;
|
|
20
|
+
}): string;
|
|
21
|
+
export {};
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
export function createCommandBlockTracker() {
|
|
2
|
+
return {
|
|
3
|
+
sequence: 0,
|
|
4
|
+
running: [],
|
|
5
|
+
};
|
|
6
|
+
}
|
|
7
|
+
function normalizeOptionalString(value) {
|
|
8
|
+
const normalized = value?.trim();
|
|
9
|
+
return normalized ? normalized : undefined;
|
|
10
|
+
}
|
|
11
|
+
function nextCommandBlockId(tracker, turnId, itemId) {
|
|
12
|
+
const stableTurnId = turnId ?? 'turn';
|
|
13
|
+
if (itemId)
|
|
14
|
+
return `command:${stableTurnId}:${itemId}`;
|
|
15
|
+
tracker.sequence += 1;
|
|
16
|
+
return `command:${stableTurnId}:${tracker.sequence}`;
|
|
17
|
+
}
|
|
18
|
+
export function beginCommandBlock(tracker, input) {
|
|
19
|
+
const itemId = normalizeOptionalString(input.itemId);
|
|
20
|
+
if (itemId) {
|
|
21
|
+
const existing = tracker.running.find((block) => block.itemId === itemId);
|
|
22
|
+
if (existing)
|
|
23
|
+
return existing.blockId;
|
|
24
|
+
}
|
|
25
|
+
const blockId = nextCommandBlockId(tracker, input.turnId, itemId);
|
|
26
|
+
tracker.running.push({
|
|
27
|
+
command: input.command.trim(),
|
|
28
|
+
blockId,
|
|
29
|
+
...(itemId ? { itemId } : {}),
|
|
30
|
+
});
|
|
31
|
+
return blockId;
|
|
32
|
+
}
|
|
33
|
+
export function claimCommandBlock(tracker, input) {
|
|
34
|
+
const itemId = normalizeOptionalString(input.itemId);
|
|
35
|
+
const command = input.command.trim();
|
|
36
|
+
const matchingIndex = itemId
|
|
37
|
+
? tracker.running.findIndex((block) => block.itemId === itemId)
|
|
38
|
+
: tracker.running.findIndex((block) => !block.itemId && block.command === command);
|
|
39
|
+
const fallbackIndex = matchingIndex >= 0
|
|
40
|
+
? matchingIndex
|
|
41
|
+
: tracker.running.findIndex((block) => !block.itemId);
|
|
42
|
+
if (fallbackIndex >= 0) {
|
|
43
|
+
const [block] = tracker.running.splice(fallbackIndex, 1);
|
|
44
|
+
return block.blockId;
|
|
45
|
+
}
|
|
46
|
+
return nextCommandBlockId(tracker, input.turnId, itemId);
|
|
47
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@canonmsg/codex-plugin",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.14.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": "^
|
|
33
|
-
"@canonmsg/core": "^
|
|
32
|
+
"@canonmsg/agent-sdk": "^3.0.0",
|
|
33
|
+
"@canonmsg/core": "^2.0.0"
|
|
34
34
|
},
|
|
35
35
|
"engines": {
|
|
36
36
|
"node": ">=18.0.0"
|
|
@@ -54,7 +54,7 @@
|
|
|
54
54
|
"devDependencies": {
|
|
55
55
|
"@types/node": "^22.0.0",
|
|
56
56
|
"typescript": "~5.7.0",
|
|
57
|
-
"vitest": "^
|
|
57
|
+
"vitest": "^4.1.8"
|
|
58
58
|
},
|
|
59
59
|
"license": "MIT"
|
|
60
60
|
}
|