@canonmsg/codex-plugin 0.9.5 → 0.9.7

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/adapter.d.ts CHANGED
@@ -66,5 +66,6 @@ export declare class CodexConversationAdapter {
66
66
  interrupt(): Promise<void>;
67
67
  runTurn(prompt: string, onEvent: (event: CodexEvent) => void, onLog?: (line: string) => void, imagePaths?: readonly string[]): Promise<CodexTurnResult>;
68
68
  private buildArgs;
69
+ private canResumeWithCurrentPolicy;
69
70
  private clearActiveProcess;
70
71
  }
package/dist/adapter.js CHANGED
@@ -1,5 +1,6 @@
1
1
  import { spawn } from 'node:child_process';
2
2
  import { createInterface } from 'node:readline';
3
+ import { isRecoverableCodexThreadError } from './error-format.js';
3
4
  export class CodexConversationAdapter {
4
5
  cwd;
5
6
  codexBin;
@@ -121,6 +122,8 @@ export class CodexConversationAdapter {
121
122
  if (isIgnorableCodexLog(trimmed))
122
123
  return;
123
124
  lastErrorText = trimmed;
125
+ if (isRecoverableCodexThreadError(trimmed))
126
+ return;
124
127
  onLog?.(trimmed);
125
128
  });
126
129
  return await new Promise((resolve, reject) => {
@@ -145,7 +148,7 @@ export class CodexConversationAdapter {
145
148
  });
146
149
  }
147
150
  buildArgs(prompt, imagePaths = []) {
148
- if (this.threadId) {
151
+ if (this.threadId && this.canResumeWithCurrentPolicy()) {
149
152
  const args = ['exec', 'resume', '--json', '--skip-git-repo-check'];
150
153
  if (this.model) {
151
154
  args.push('-m', this.model);
@@ -168,10 +171,12 @@ export class CodexConversationAdapter {
168
171
  args.push(this.threadId, prompt);
169
172
  return args;
170
173
  }
174
+ if (this.threadId) {
175
+ this.threadId = null;
176
+ }
171
177
  const args = ['exec', '--json', '--color', 'never', '-C', this.cwd, '--skip-git-repo-check'];
172
178
  const execMode = resolveExecMode({
173
179
  sandbox: this.sandbox,
174
- approvalPolicy: this.legacyApprovalPolicy,
175
180
  fullAuto: this.fullAuto,
176
181
  bypassApprovalsAndSandbox: this.bypassApprovalsAndSandbox,
177
182
  });
@@ -202,6 +207,12 @@ export class CodexConversationAdapter {
202
207
  args.push(prompt);
203
208
  return args;
204
209
  }
210
+ canResumeWithCurrentPolicy() {
211
+ if (this.bypassApprovalsAndSandbox || this.fullAuto) {
212
+ return true;
213
+ }
214
+ return this.sandbox === null;
215
+ }
205
216
  clearActiveProcess() {
206
217
  if (this.interruptTimer) {
207
218
  clearTimeout(this.interruptTimer);
@@ -240,18 +251,8 @@ function resolveExecMode(input) {
240
251
  if (input.fullAuto) {
241
252
  return { fullAuto: true, bypassApprovalsAndSandbox: false };
242
253
  }
243
- if (shouldTranslateLegacyApprovalMode(input)) {
244
- return { fullAuto: true, bypassApprovalsAndSandbox: false };
245
- }
246
254
  return { fullAuto: false, bypassApprovalsAndSandbox: false };
247
255
  }
248
- function shouldTranslateLegacyApprovalMode(input) {
249
- // Newer Codex CLI releases no longer accept --ask-for-approval for `exec`.
250
- // Keep the compatibility shim isolated here so the rest of the adapter only
251
- // deals with the supported execution switches.
252
- return input.approvalPolicy === 'never'
253
- && (input.sandbox === 'workspace-write' || input.sandbox == null);
254
- }
255
256
  function isIgnorableCodexLog(line) {
256
257
  return [
257
258
  'Reading additional input from stdin...',
package/dist/host.js CHANGED
@@ -145,6 +145,48 @@ function resolveExecutionFallbackReason(environment) {
145
145
  ? null
146
146
  : environment.reason;
147
147
  }
148
+ function stringArg(args, key) {
149
+ const value = args[key];
150
+ return typeof value === 'string' && value.trim() ? value.trim() : undefined;
151
+ }
152
+ function boolArg(args, key) {
153
+ return args[key] === true;
154
+ }
155
+ function resolveCodexEffectiveRuntimePolicy(input) {
156
+ const model = input.config?.model ?? stringArg(input.args, 'model');
157
+ const permissionMode = input.config?.permissionMode ?? input.permissionEnvelope.defaultPermissionMode;
158
+ if (permissionMode
159
+ && !input.permissionEnvelope.availablePermissionModes.some((option) => option.value === permissionMode)) {
160
+ throw new ExecutionEnvironmentError(`Permission mode "${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.');
161
+ }
162
+ const approvalOverride = mapCanonPermissionToCodex(permissionMode);
163
+ const defaultSandbox = (stringArg(input.args, 'sandbox') ?? null);
164
+ const defaultApprovalPolicy = (stringArg(input.args, 'ask-for-approval') ?? null);
165
+ const sandbox = approvalOverride ? approvalOverride.sandbox : defaultSandbox;
166
+ const approvalPolicy = approvalOverride ? null : defaultApprovalPolicy;
167
+ const fullAuto = approvalOverride ? approvalOverride.fullAuto : boolArg(input.args, 'full-auto');
168
+ const bypassApprovalsAndSandbox = approvalOverride
169
+ ? approvalOverride.bypassApprovalsAndSandbox
170
+ : boolArg(input.args, 'dangerously-bypass-approvals-and-sandbox');
171
+ const fingerprint = buildCodexThreadPolicyFingerprint({
172
+ baseCwd: input.environment.baseCwd,
173
+ executionMode: input.environment.mode,
174
+ permissionMode: permissionMode ?? null,
175
+ sandbox,
176
+ approvalPolicy,
177
+ fullAuto,
178
+ bypassApprovalsAndSandbox,
179
+ });
180
+ return {
181
+ ...(model ? { model } : {}),
182
+ ...(permissionMode ? { permissionMode } : {}),
183
+ sandbox,
184
+ approvalPolicy,
185
+ fullAuto,
186
+ bypassApprovalsAndSandbox,
187
+ fingerprint,
188
+ };
189
+ }
148
190
  function buildCanonPrompt(input) {
149
191
  return buildCanonHostPrompt({
150
192
  hostLabel: 'Codex',
@@ -184,6 +226,11 @@ export async function main() {
184
226
  },
185
227
  strict: true,
186
228
  });
229
+ if (typeof args['ask-for-approval'] === 'string') {
230
+ console.error('[canon-codex] --ask-for-approval is no longer supported by Canon. Use --full-auto, --sandbox, or Canon permission modes instead.');
231
+ process.exitCode = 1;
232
+ return;
233
+ }
187
234
  workingDir = (typeof args.cwd === 'string' ? args.cwd : null) || process.cwd();
188
235
  const workspaceDiscovery = buildConfiguredWorkspaceOptionsWithRoots({
189
236
  primaryCwd: workingDir,
@@ -196,9 +243,6 @@ export async function main() {
196
243
  for (const warning of workspaceDiscovery.warnings) {
197
244
  console.error(`[canon-codex] ${warning}`);
198
245
  }
199
- if (typeof args['ask-for-approval'] === 'string') {
200
- 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.');
201
- }
202
246
  const codexBin = typeof args['codex-bin'] === 'string' ? args['codex-bin'] : 'codex';
203
247
  const codexCliStatus = detectCodexCliVersion(codexBin);
204
248
  if (codexCliStatus.version) {
@@ -311,10 +355,19 @@ export async function main() {
311
355
  });
312
356
  }
313
357
  function writeState(session) {
358
+ const appliedAt = Date.now();
359
+ const controlState = {};
360
+ if (session.state.model !== undefined) {
361
+ controlState.model = { value: session.state.model, source: 'applied', appliedAt };
362
+ }
363
+ if (session.state.permissionMode !== undefined) {
364
+ controlState.permissionMode = { value: session.state.permissionMode, source: 'applied', appliedAt };
365
+ }
314
366
  runtimeState.writeSessionState(session.conversationId, {
315
367
  lastError: session.state.lastError,
316
368
  model: session.state.model,
317
369
  permissionMode: session.state.permissionMode,
370
+ controlState,
318
371
  cwd: session.cwd,
319
372
  executionMode: session.environment.mode,
320
373
  ...(session.environment.branch ? { executionBranch: session.environment.branch } : {}),
@@ -433,38 +486,17 @@ export async function main() {
433
486
  });
434
487
  try {
435
488
  const sessionCwd = environment.cwd;
436
- const sessionModel = config?.model ?? (typeof args.model === 'string' ? args.model : undefined);
437
- const modelGuard = buildCodexModelGuardMessage(sessionModel, codexCliStatus);
489
+ const policy = resolveCodexEffectiveRuntimePolicy({
490
+ args,
491
+ config,
492
+ permissionEnvelope: codexPermissionEnvelope,
493
+ environment,
494
+ });
495
+ const modelGuard = buildCodexModelGuardMessage(policy.model, codexCliStatus);
438
496
  if (modelGuard) {
439
497
  throw new ExecutionEnvironmentError(modelGuard, modelGuard);
440
498
  }
441
- const effectivePermissionMode = config?.permissionMode ?? codexPermissionEnvelope.defaultPermissionMode;
442
- if (effectivePermissionMode
443
- && !codexPermissionEnvelope.availablePermissionModes.some((option) => option.value === effectivePermissionMode)) {
444
- throw new ExecutionEnvironmentError(`Permission mode "${effectivePermissionMode}" 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.');
445
- }
446
- const approvalOverride = mapCanonPermissionToCodex(effectivePermissionMode);
447
- const defaultSandbox = (typeof args.sandbox === 'string' ? args.sandbox : null);
448
- const defaultFullAuto = Boolean(args['full-auto']);
449
- const defaultBypass = Boolean(args['dangerously-bypass-approvals-and-sandbox']);
450
- const legacyApprovalPolicy = (typeof args['ask-for-approval'] === 'string'
451
- ? args['ask-for-approval']
452
- : null);
453
- const effectiveSandbox = approvalOverride ? approvalOverride.sandbox : defaultSandbox;
454
- const effectiveFullAuto = approvalOverride ? approvalOverride.fullAuto : defaultFullAuto;
455
- const effectiveBypass = approvalOverride
456
- ? approvalOverride.bypassApprovalsAndSandbox
457
- : defaultBypass;
458
- const policyFingerprint = buildCodexThreadPolicyFingerprint({
459
- baseCwd: environment.baseCwd,
460
- executionMode: environment.mode,
461
- permissionMode: effectivePermissionMode ?? null,
462
- sandbox: effectiveSandbox,
463
- approvalPolicy: approvalOverride ? null : legacyApprovalPolicy,
464
- fullAuto: effectiveFullAuto,
465
- bypassApprovalsAndSandbox: effectiveBypass,
466
- });
467
- const storedThreadId = loadStoredThreadId(runtimeId, agentId, conversationId, environment.baseCwd, environment.mode, policyFingerprint);
499
+ const storedThreadId = loadStoredThreadId(runtimeId, agentId, conversationId, environment.baseCwd, environment.mode, policy.fingerprint);
468
500
  const session = {
469
501
  conversationId,
470
502
  cwd: sessionCwd,
@@ -473,23 +505,23 @@ export async function main() {
473
505
  cwd: sessionCwd,
474
506
  threadId: storedThreadId,
475
507
  codexBin,
476
- model: sessionModel ?? null,
477
- sandbox: effectiveSandbox,
478
- approvalPolicy: approvalOverride ? null : legacyApprovalPolicy,
508
+ model: policy.model ?? null,
509
+ sandbox: policy.sandbox,
510
+ approvalPolicy: policy.approvalPolicy,
479
511
  codexProfile: typeof args['codex-profile'] === 'string' ? args['codex-profile'] : null,
480
512
  addDirs: args['add-dir'] ?? [],
481
513
  configOverrides: args.config ?? [],
482
- fullAuto: effectiveFullAuto,
483
- bypassApprovalsAndSandbox: effectiveBypass,
514
+ fullAuto: policy.fullAuto,
515
+ bypassApprovalsAndSandbox: policy.bypassApprovalsAndSandbox,
484
516
  }),
485
517
  queue: [],
486
518
  running: false,
487
519
  state: {
488
- model: sessionModel,
489
- permissionMode: effectivePermissionMode,
520
+ model: policy.model,
521
+ permissionMode: policy.permissionMode,
490
522
  state: 'idle',
491
523
  },
492
- policyFingerprint,
524
+ policyFingerprint: policy.fingerprint,
493
525
  turnState: 'idle',
494
526
  currentTurnId: null,
495
527
  currentTurnOpenedAt: null,
@@ -583,6 +615,7 @@ export async function main() {
583
615
  metadata: {
584
616
  turnSemantics: 'turn_complete',
585
617
  turnComplete: true,
618
+ replyBehavior: 'suppress_auto_reply',
586
619
  },
587
620
  }).catch(() => { });
588
621
  return;
@@ -22,10 +22,6 @@ function codexOptionsThrough(mode) {
22
22
  const index = CODEX_PERMISSION_OPTIONS.findIndex((option) => option.value === mode);
23
23
  return index >= 0 ? CODEX_PERMISSION_OPTIONS.slice(0, index + 1) : [];
24
24
  }
25
- function isLegacyFullAuto(args) {
26
- return args['ask-for-approval'] === 'never'
27
- && (args.sandbox === 'workspace-write' || args.sandbox == null);
28
- }
29
25
  export function deriveCodexPermissionEnvelope(args) {
30
26
  if (args.sandbox === 'read-only') {
31
27
  return {
@@ -46,7 +42,7 @@ export function deriveCodexPermissionEnvelope(args) {
46
42
  availablePermissionModes: [...CODEX_PERMISSION_OPTIONS],
47
43
  };
48
44
  }
49
- if (args['full-auto'] || isLegacyFullAuto(args)) {
45
+ if (args['full-auto']) {
50
46
  return {
51
47
  defaultPermissionMode: 'full-auto',
52
48
  availablePermissionModes: codexOptionsThrough('full-auto'),
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@canonmsg/codex-plugin",
3
- "version": "0.9.5",
3
+ "version": "0.9.7",
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": "^1.1.0",
33
- "@canonmsg/core": "^0.15.1"
32
+ "@canonmsg/agent-sdk": "^1.1.2",
33
+ "@canonmsg/core": "^0.15.4"
34
34
  },
35
35
  "engines": {
36
36
  "node": ">=18.0.0"