@canonmsg/codex-plugin 0.29.3 → 0.29.4

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
@@ -45,6 +45,8 @@ export interface CodexServerRequest {
45
45
  params: Record<string, unknown>;
46
46
  }
47
47
  export interface CodexRunTurnOptions {
48
+ /** Original human text, before Canon adds conversation/reply context. */
49
+ skillInvocationText?: string;
48
50
  planMode?: boolean;
49
51
  onServerRequest?: (request: CodexServerRequest) => Promise<unknown>;
50
52
  }
@@ -36,6 +36,7 @@ export declare class CodexAppServerAdapter {
36
36
  private threadId;
37
37
  private loadedThreadId;
38
38
  private resolvedModel;
39
+ private observedSettings;
39
40
  private resolvedReasoningEffort;
40
41
  private currentTurnId;
41
42
  private requestSeq;
@@ -67,6 +68,10 @@ export declare class CodexAppServerAdapter {
67
68
  });
68
69
  getThreadId(): string | null;
69
70
  getResolvedModel(): string | null;
71
+ getObservedSettings(): Readonly<{
72
+ model?: string;
73
+ effort?: string;
74
+ }>;
70
75
  getResolvedReasoningEffort(): string | null;
71
76
  clearThreadId(): void;
72
77
  setModel(model: string | null): void;
@@ -17,6 +17,8 @@ export class CodexAppServerAdapter {
17
17
  threadId;
18
18
  loadedThreadId = null;
19
19
  resolvedModel = null;
20
+ // Public observations never inherit requested settings or fallback defaults.
21
+ observedSettings = {};
20
22
  resolvedReasoningEffort;
21
23
  currentTurnId = null;
22
24
  requestSeq = 1;
@@ -53,16 +55,21 @@ export class CodexAppServerAdapter {
53
55
  getResolvedModel() {
54
56
  return this.resolvedModel ?? this.model;
55
57
  }
58
+ getObservedSettings() {
59
+ return { ...this.observedSettings };
60
+ }
56
61
  getResolvedReasoningEffort() {
57
62
  return this.resolvedReasoningEffort ?? this.reasoningEffort;
58
63
  }
59
64
  clearThreadId() {
65
+ this.observedSettings = {};
60
66
  this.threadId = null;
61
67
  this.loadedThreadId = null;
62
68
  this.resolvedModel = null;
63
69
  this.resolvedReasoningEffort = this.reasoningEffort;
64
70
  }
65
71
  setModel(model) {
72
+ this.observedSettings = {};
66
73
  this.model = model;
67
74
  this.resolvedModel = model;
68
75
  }
@@ -78,6 +85,7 @@ export class CodexAppServerAdapter {
78
85
  const next = effort && effort.trim() ? effort.trim() : null;
79
86
  if (next === this.reasoningEffort && next === this.resolvedReasoningEffort)
80
87
  return;
88
+ this.observedSettings = { model: this.observedSettings.model };
81
89
  this.reasoningEffort = next;
82
90
  this.resolvedReasoningEffort = next;
83
91
  if (this.threadId && this.loadedThreadId === this.threadId) {
@@ -110,6 +118,7 @@ export class CodexAppServerAdapter {
110
118
  return await this.sendRequest(method, params);
111
119
  }
112
120
  close() {
121
+ this.observedSettings = {};
113
122
  this.child?.kill('SIGTERM');
114
123
  this.child = null;
115
124
  this.initialized = false;
@@ -171,7 +180,7 @@ export class CodexAppServerAdapter {
171
180
  turnPromise.catch(() => { });
172
181
  const turnStarted = await this.sendRequest('turn/start', {
173
182
  threadId: this.threadId,
174
- input: await this.buildTurnInput(prompt, imagePaths),
183
+ input: await this.buildTurnInput(prompt, imagePaths, options.skillInvocationText),
175
184
  ...(this.model ? { model: this.model } : {}),
176
185
  ...this.sandboxPolicyPayload(_extraAddDirs),
177
186
  collaborationMode: {
@@ -216,7 +225,9 @@ export class CodexAppServerAdapter {
216
225
  }
217
226
  rememberResolvedModel(result) {
218
227
  const thread = result.thread;
219
- this.resolvedModel = readString(thread, 'model') ?? readString(result, 'model') ?? this.resolvedModel;
228
+ const model = readString(thread, 'model') ?? readString(result, 'model');
229
+ this.observedSettings = { ...this.observedSettings, model: model ?? undefined };
230
+ this.resolvedModel = model ?? this.resolvedModel;
220
231
  }
221
232
  sandboxPolicyPayload(extraAddDirs) {
222
233
  if (this.bypassApprovalsAndSandbox || this.sandbox === 'danger-full-access') {
@@ -290,22 +301,27 @@ export class CodexAppServerAdapter {
290
301
  } while (cursor);
291
302
  return models;
292
303
  }
293
- async buildTurnInput(prompt, imagePaths) {
294
- const skillPrompt = parseSkillSlashPrompt(prompt);
304
+ async buildTurnInput(prompt, imagePaths, skillInvocationText) {
305
+ const skillPrompt = parseSkillSlashPrompt(skillInvocationText ?? prompt);
295
306
  const input = [];
296
307
  if (skillPrompt) {
297
308
  const skills = await this.listSkills().catch(() => []);
298
309
  const selected = skills.find((skill) => skill.name.toLowerCase() === skillPrompt.name.toLowerCase());
299
310
  if (selected) {
300
311
  input.push({ type: 'skill', name: selected.name, path: selected.path });
301
- if (skillPrompt.prompt.trim()) {
302
- input.push({ type: 'text', text: skillPrompt.prompt, text_elements: [] });
312
+ // Resolve the native skill from the actual message, retaining the
313
+ // framed prompt as context instead of parsing quoted/history text.
314
+ const text = skillInvocationText === undefined ? skillPrompt.prompt : prompt;
315
+ if (text.trim()) {
316
+ input.push({ type: 'text', text, text_elements: [] });
303
317
  }
304
318
  }
305
319
  else {
306
320
  input.push({
307
321
  type: 'text',
308
- text: `$${skillPrompt.name}${skillPrompt.prompt.trim() ? ` ${skillPrompt.prompt.trim()}` : ''}`,
322
+ text: skillInvocationText === undefined
323
+ ? `$${skillPrompt.name}${skillPrompt.prompt.trim() ? ` ${skillPrompt.prompt.trim()}` : ''}`
324
+ : prompt,
309
325
  text_elements: [],
310
326
  });
311
327
  }
@@ -333,6 +349,7 @@ export class CodexAppServerAdapter {
333
349
  this.currentOnLog?.(trimmed);
334
350
  });
335
351
  child.on('close', (code) => {
352
+ this.observedSettings = {};
336
353
  const message = `Codex app-server exited${code === null ? '' : ` with code ${code}`}`;
337
354
  this.initialized = false;
338
355
  this.child = null;
@@ -428,6 +445,7 @@ export class CodexAppServerAdapter {
428
445
  const settings = isRecord(params.threadSettings) ? params.threadSettings : {};
429
446
  const model = readString(settings, 'model') ?? null;
430
447
  const effort = readString(settings, 'effort') ?? null;
448
+ this.observedSettings = { model: model ?? undefined, effort: effort ?? undefined };
431
449
  this.resolvedModel = model ?? this.resolvedModel;
432
450
  this.resolvedReasoningEffort = effort;
433
451
  this.currentOnEvent?.({ type: 'settings.updated', model, effort });
@@ -435,6 +453,7 @@ export class CodexAppServerAdapter {
435
453
  }
436
454
  if (method === 'model/rerouted') {
437
455
  const model = readString(params, 'toModel') ?? null;
456
+ this.observedSettings = { model: model ?? undefined };
438
457
  this.resolvedModel = model ?? this.resolvedModel;
439
458
  this.currentOnEvent?.({
440
459
  type: 'settings.updated',
package/dist/host.d.ts CHANGED
@@ -1,10 +1,13 @@
1
1
  #!/usr/bin/env node
2
2
  import { type TurnArtifactRoutingDecision, type TurnArtifactRoutingMode, type RecoveryCheckpointTracker } from '@canonmsg/coding-agent-host';
3
- import { type CanonRuntimeCommandDescriptor, type CanonRuntimeDescriptor, type CanonRuntimePresentationPolicy, type ExecutionEnvironmentMode, type PreparedExecutionEnvironment, type WorkspaceOption, type CanonWorkspaceRootMetadata, type RuntimeStreamingPayload, type TurnLifecycleState, type TurnOutputBlock, type TurnVerbosity, type TurnVerbosityConfig } from '@canonmsg/core';
4
- import { type CodexSandboxMode } from './adapter.js';
5
- import { type CodexSkillMetadata } from './app-server-adapter.js';
3
+ import { type CanonRuntimeCommandDescriptor, type CanonRuntimeDescriptor, type CanonRuntimeFact, type CanonRuntimePresentationPolicy, type ExecutionEnvironmentMode, type PreparedExecutionEnvironment, type WorkspaceOption, type CanonWorkspaceRootMetadata, type DeliveryIntent, type RuntimeStreamingPayload, type TurnLifecycleState, type TurnOutputBlock, type TurnVerbosity, type TurnVerbosityConfig } from '@canonmsg/core';
4
+ import type { AgentReplyAuthorityV1 } from '@canonmsg/backend-contracts';
5
+ import { CodexConversationAdapter, type CodexSandboxMode } from './adapter.js';
6
+ import { CodexAppServerAdapter, type CodexSkillMetadata } from './app-server-adapter.js';
6
7
  import { deriveCodexPermissionEnvelope } from './permission-mode.js';
8
+ import { type CommandBlockTracker } from './turn-activity.js';
7
9
  import { type CodexControlOption } from './model-catalog.js';
10
+ type CodexAdapter = CodexConversationAdapter | CodexAppServerAdapter;
8
11
  interface HostSessionState {
9
12
  lastError?: string;
10
13
  model?: string;
@@ -21,6 +24,63 @@ export declare function buildCodexInitialSessionState(input: {
21
24
  permissionMode?: string;
22
25
  effort?: string | null;
23
26
  }): HostSessionState;
27
+ type ArtifactRoutingMode = 'workspace-generated' | 'disabled';
28
+ interface Session {
29
+ conversationId: string;
30
+ cwd: string;
31
+ environment: PreparedExecutionEnvironment;
32
+ adapter: CodexAdapter;
33
+ queue: Array<{
34
+ prompt: string;
35
+ skillInvocationText?: string;
36
+ planMode?: boolean;
37
+ intent: DeliveryIntent;
38
+ sourceMessageId?: string | null;
39
+ markAccepted?: boolean;
40
+ imagePaths?: string[];
41
+ mediaAddDirs?: string[];
42
+ artifactRoutingMode?: ArtifactRoutingMode;
43
+ canUseCodexAppTools?: boolean;
44
+ /**
45
+ * Resolved when this message arrived and carried here, so a prompt that
46
+ * waits in the queue still runs under the answer it arrived with. Absent on
47
+ * a continuation prompt (a plan-review result), which inherits whatever the
48
+ * conversation last resolved.
49
+ */
50
+ turnVerbosity?: TurnVerbosity;
51
+ requestingUserId: string | null;
52
+ replyAuthority: AgentReplyAuthorityV1 | null;
53
+ }>;
54
+ running: boolean;
55
+ state: HostSessionState;
56
+ policyFingerprint: string;
57
+ turnState: TurnLifecycleState;
58
+ currentTurnId: string | null;
59
+ currentTurnOpenedAt: number | null;
60
+ currentTurnUpdatedAt: number | null;
61
+ currentTurnCanUseCodexAppTools: boolean;
62
+ currentReplyAuthority: AgentReplyAuthorityV1 | null;
63
+ /** Cancels Canon interactions created by the currently running Codex turn. */
64
+ currentTurnAbortController: AbortController | null;
65
+ /**
66
+ * The verbosity the RUNNING turn was opened with. Promoted off the queue
67
+ * entry at `runNextTurn` and never re-read mid-turn, so the live writer, the
68
+ * turn-state writer and the final's trail cannot disagree with each other.
69
+ */
70
+ turnVerbosity: TurnVerbosity;
71
+ /** The running turn called `no_reply`: end it without posting anything. */
72
+ currentTurnSilenced: boolean;
73
+ activeSelfContextId: string | null;
74
+ lastAcceptedIntent: DeliveryIntent | null;
75
+ resetRequested: boolean;
76
+ lastActivity: number;
77
+ typingKeepaliveTimer: ReturnType<typeof setInterval> | null;
78
+ closed: boolean;
79
+ turnLiveText: string;
80
+ turnBlocks: TurnOutputBlock[];
81
+ turnCommandBlocks: CommandBlockTracker;
82
+ }
83
+ export declare function buildCodexSessionFacts(session?: Pick<Session, 'closed' | 'adapter'>): CanonRuntimeFact[];
24
84
  export declare function createCodexRecoveryCheckpointTracker(persist: (messageId: string) => boolean): RecoveryCheckpointTracker;
25
85
  /** Conservative fallback used only when native app-server discovery is unavailable. */
26
86
  export declare const CODEX_EFFORT_OPTIONS: readonly CodexControlOption[];
package/dist/host.js CHANGED
@@ -69,6 +69,23 @@ export function buildCodexInitialSessionState(input) {
69
69
  state: 'idle',
70
70
  };
71
71
  }
72
+ export function buildCodexSessionFacts(session) {
73
+ if (!session || session.closed)
74
+ return [];
75
+ const adapter = session.adapter;
76
+ const appServer = adapter instanceof CodexAppServerAdapter;
77
+ const facts = [{
78
+ id: 'runtime', label: 'Runtime', value: appServer ? 'Codex app-server' : 'Codex CLI', group: 'runtime',
79
+ }];
80
+ if (appServer) {
81
+ const observed = adapter.getObservedSettings();
82
+ if (observed.model)
83
+ facts.push({ id: 'model', label: 'Model', value: observed.model, group: 'model' });
84
+ if (observed.effort)
85
+ facts.push({ id: 'reasoning', label: 'Reasoning', value: observed.effort, group: 'model' });
86
+ }
87
+ return facts;
88
+ }
72
89
  const MAX_SESSIONS = 12;
73
90
  export function createCodexRecoveryCheckpointTracker(persist) {
74
91
  return createRecoveryCheckpointTracker(persist);
@@ -133,10 +150,10 @@ export function buildCodexRuntimeDescriptor(input) {
133
150
  const commands = [
134
151
  {
135
152
  id: 'runtime-status',
136
- label: 'Runtime status',
137
- description: 'Open Codex runtime details.',
153
+ label: 'Session info',
154
+ description: 'Open Canon session information.',
138
155
  primitive: 'runtime.status',
139
- aliases: ['status'],
156
+ aliases: ['session-info'],
140
157
  category: 'details',
141
158
  placements: ['composer_slash', 'command_palette'],
142
159
  availability: ['always'],
@@ -1338,6 +1355,7 @@ export async function main() {
1338
1355
  function enqueuePrompt(session, prompt, intent = 'queue', toFront = false, sourceMessageId, markAccepted = false, imagePaths = [], mediaAddDirs = [], planMode = false, turn = {}) {
1339
1356
  const nextPrompt = {
1340
1357
  prompt,
1358
+ skillInvocationText: turn.skillInvocationText,
1341
1359
  intent,
1342
1360
  sourceMessageId,
1343
1361
  markAccepted,
@@ -1884,6 +1902,7 @@ export async function main() {
1884
1902
  if (session.running && deliveryIntent === 'interrupt') {
1885
1903
  enqueuePrompt(session, prompt, deliveryIntent, true, input.message.id, shouldMarkAccepted, imagePaths, mediaAddDirs, planCommand.planMode, {
1886
1904
  ...resolveCodexTurnModes(participantContext, input.message),
1905
+ skillInvocationText: input.message.senderType === 'human' ? input.message.text ?? undefined : undefined,
1887
1906
  replyAuthority: input.replyAuthority ?? null,
1888
1907
  });
1889
1908
  console.error(`[canon-codex] [${input.conversationId.slice(0, 8)}] Interrupting current turn for explicit human send-now`);
@@ -1895,6 +1914,7 @@ export async function main() {
1895
1914
  }
1896
1915
  enqueuePrompt(session, prompt, deliveryIntent, false, input.message.id, shouldMarkAccepted, imagePaths, mediaAddDirs, planCommand.planMode, {
1897
1916
  ...resolveCodexTurnModes(participantContext, input.message),
1917
+ skillInvocationText: input.message.senderType === 'human' ? input.message.text ?? undefined : undefined,
1898
1918
  replyAuthority: input.replyAuthority ?? null,
1899
1919
  });
1900
1920
  }
@@ -2126,6 +2146,7 @@ export async function main() {
2126
2146
  };
2127
2147
  const runTurnOnce = () => session.adapter.runTurn(turnPrompt, handleCodexEvent, logCodexLine, turnImagePaths, turnMediaAddDirs, {
2128
2148
  planMode: nextTurn.planMode,
2149
+ skillInvocationText: nextTurn.skillInvocationText,
2129
2150
  onServerRequest: (request) => handleCodexServerRequest(session, request, nextTurn.requestingUserId ?? null, nextTurn.sourceMessageId ?? null),
2130
2151
  });
2131
2152
  let result = await runTurnOnce();
@@ -2584,12 +2605,9 @@ export async function main() {
2584
2605
  return;
2585
2606
  await runtimeState.writeRuntimeInfo(conversationId, {
2586
2607
  surfaceMode: 'host',
2587
- descriptor: {
2588
- coreControls: [],
2589
- supportsInterrupt: descriptor.supportsInterrupt,
2590
- supportsInputInterrupt: descriptor.supportsInputInterrupt,
2591
- streamingTextMode: descriptor.streamingTextMode,
2592
- },
2608
+ // The shared publisher sanitizes configuration while retaining commands.
2609
+ descriptor,
2610
+ facts: buildCodexSessionFacts(sessions.get(conversationId)),
2593
2611
  });
2594
2612
  }));
2595
2613
  const failure = results.find((result) => result.status === 'rejected');
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@canonmsg/codex-plugin",
3
- "version": "0.29.3",
3
+ "version": "0.29.4",
4
4
  "description": "Canon host integration for Codex CLI",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
@@ -32,7 +32,7 @@
32
32
  "@canonmsg/agent-sdk": "^10.2.1",
33
33
  "@canonmsg/agent-tools": "^0.9.0",
34
34
  "@canonmsg/coding-agent-host": "^0.7.0",
35
- "@canonmsg/core": "^12.3.0",
35
+ "@canonmsg/core": "^12.3.1",
36
36
  "@canonmsg/rich-cards": "^0.10.4"
37
37
  },
38
38
  "engines": {