@canonmsg/codex-plugin 0.6.5 โ†’ 0.7.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 CHANGED
@@ -37,7 +37,7 @@ 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 completed assistant messages and tool activity, but not token-by-token text deltas. v1 therefore reports live status and publishes the final reply when the turn completes, instead of true token streaming.
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.
41
41
 
42
42
  ## Working directory
43
43
 
@@ -45,12 +45,22 @@ The stable `codex exec --json` surface exposes completed assistant messages and
45
45
  canon-codex --cwd /path/to/project
46
46
  ```
47
47
 
48
+ Advertise multiple project choices to the Canon app:
49
+
50
+ ```bash
51
+ canon-codex --cwd ~/dev --workspace ~/dev/canon --workspace ~/dev/yumyumv2
52
+ ```
53
+
54
+ `--cwd` is the default workspace. Each `--workspace` value appears as a selectable workspace during session creation. Worktree mode creates a per-conversation git worktree under `~/.canon/conversation-worktrees`; shared-workspace mode runs directly in the selected directory.
55
+
48
56
  Useful flags:
49
57
 
50
58
  ```bash
51
59
  canon-codex --cwd /path/to/project --model gpt-5.4 --full-auto
52
60
  ```
53
61
 
62
+ 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.
63
+
54
64
  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
65
 
56
66
  Local smoke test:
@@ -37,6 +37,12 @@ interface HostWorkspaceResolverOption {
37
37
  id: string;
38
38
  cwd: string;
39
39
  }
40
+ export declare const HOST_ADMISSION_ACTION_CAPABILITIES: Readonly<{
41
+ canStartDirectConversation: false;
42
+ canSendContactRequest: false;
43
+ canApprovePendingContactRequests: false;
44
+ canRejectPendingContactRequests: false;
45
+ }>;
40
46
  export declare function buildCanonHostPrompt(input: {
41
47
  hostLabel: string;
42
48
  content: string;
@@ -81,12 +87,33 @@ export declare function buildHydratedInboundContext(input: {
81
87
  hydratedFromPage: boolean;
82
88
  };
83
89
  export declare function publishHostAgentRuntime(agentId: string, clientType: AgentClientType, runtime: AgentRuntime): Promise<void>;
84
- export declare function readHostSessionConfig<TExtra extends string = never>(raw: unknown, extraStringFields?: readonly TExtra[]): (SessionWorkspaceConfig & Partial<Record<TExtra, string>>) | null;
90
+ export declare function publishHostSessionSnapshots(input: {
91
+ conversationIds: string[];
92
+ agentId: string;
93
+ clientType: AgentClientType;
94
+ runtime: AgentRuntime;
95
+ workspaceOptions: HostWorkspaceResolverOption[];
96
+ defaultCwd: string;
97
+ liveSessionConfigByConversation?: ReadonlyMap<string, {
98
+ model?: string;
99
+ permissionMode?: string;
100
+ effort?: string;
101
+ runtimeControlValues?: Record<string, string>;
102
+ workspaceId?: string;
103
+ executionMode?: SessionWorkspaceConfig['executionMode'];
104
+ executionBranch?: string | null;
105
+ }>;
106
+ }): Promise<void>;
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;
85
110
  export declare function loadHostSessionConfig<TExtra extends string = never>(input: {
86
111
  conversationId: string;
87
112
  agentId: string;
88
113
  extraStringFields?: readonly TExtra[];
89
- }): Promise<(SessionWorkspaceConfig & Partial<Record<TExtra, string>>) | null>;
114
+ }): Promise<(SessionWorkspaceConfig & Partial<Record<TExtra, string>> & {
115
+ runtimeControlValues?: Record<string, string>;
116
+ }) | null>;
90
117
  export declare function resolveHostWorkspaceCwd(input: {
91
118
  workspaceOptions: HostWorkspaceResolverOption[];
92
119
  config: {
@@ -11,7 +11,13 @@
11
11
  * behavior here, update that copy too and adjust the shared golden
12
12
  * fixture test (`packages/codex-plugin/src/host-runtime.test.ts`).
13
13
  */
14
- import { buildBehaviorPolicyLines, buildParticipationHistorySnapshot, buildWorkSessionsPromptLines, mergeWorkSessionContexts, normalizeOptionalString, readSessionWorkspaceConfig, resolveConfiguredWorkspaceCwd, rtdbRead, rtdbWrite, } from '@canonmsg/core';
14
+ import { buildAgentSessionSnapshot, buildConversationWorktreeSpec, buildBehaviorPolicyLines, buildParticipationHistorySnapshot, buildWorkSessionsPromptLines, mergeWorkSessionContexts, normalizeOptionalString, patchAgentSessionSnapshot, readSessionWorkspaceConfig, resolveConfiguredWorkspaceCwd, rtdbRead, rtdbWrite, } from '@canonmsg/core';
15
+ export const HOST_ADMISSION_ACTION_CAPABILITIES = Object.freeze({
16
+ canStartDirectConversation: false,
17
+ canSendContactRequest: false,
18
+ canApprovePendingContactRequests: false,
19
+ canRejectPendingContactRequests: false,
20
+ });
15
21
  export function buildCanonHostPrompt(input) {
16
22
  const resolvedWorkSessions = mergeWorkSessionContexts(input.workSession, input.workSessions);
17
23
  return [
@@ -69,7 +75,21 @@ function describeContactCard(card) {
69
75
  if (card.about)
70
76
  parts.push(`about: ${card.about}`);
71
77
  const identity = `๐Ÿ“‡ Contact card: "${card.displayName}" (${parts.join(' ยท ')}).`;
72
- const hint = `This card is informational only in host mode. Canon does not currently expose a host-side tool here for starting a new direct conversation or sending a contact request to userId ${card.userId}.`;
78
+ const missingCapabilities = [
79
+ !HOST_ADMISSION_ACTION_CAPABILITIES.canStartDirectConversation
80
+ ? 'start a direct conversation'
81
+ : null,
82
+ !HOST_ADMISSION_ACTION_CAPABILITIES.canSendContactRequest
83
+ ? 'send a contact request'
84
+ : null,
85
+ !HOST_ADMISSION_ACTION_CAPABILITIES.canApprovePendingContactRequests
86
+ ? 'approve pending requests'
87
+ : null,
88
+ !HOST_ADMISSION_ACTION_CAPABILITIES.canRejectPendingContactRequests
89
+ ? 'reject pending requests'
90
+ : null,
91
+ ].filter(Boolean).join(', ');
92
+ const hint = `This host can inspect the card, but Canon admission actions are missing here. Missing capabilities: ${missingCapabilities}. Use another Canon surface for userId ${card.userId}.`;
73
93
  return `${identity}\n${hint}`;
74
94
  }
75
95
  function describeAttachment(attachment, materialized) {
@@ -116,6 +136,70 @@ export async function publishHostAgentRuntime(agentId, clientType, runtime) {
116
136
  updatedAt: { '.sv': 'timestamp' },
117
137
  });
118
138
  }
139
+ export async function publishHostSessionSnapshots(input) {
140
+ if (input.conversationIds.length === 0) {
141
+ return;
142
+ }
143
+ await Promise.all(input.conversationIds.map(async (conversationId) => {
144
+ const persistedConfig = await loadHostSessionConfig({
145
+ conversationId,
146
+ agentId: input.agentId,
147
+ extraStringFields: ['permissionMode'],
148
+ });
149
+ const liveConfig = input.liveSessionConfigByConversation?.get(conversationId) ?? null;
150
+ const mergedConfig = {
151
+ ...(persistedConfig ?? {}),
152
+ ...(liveConfig ?? {}),
153
+ };
154
+ const snapshot = buildAgentSessionSnapshot({
155
+ conversationId,
156
+ agentId: input.agentId,
157
+ runtime: {
158
+ ...input.runtime,
159
+ clientType: input.clientType,
160
+ hostMode: true,
161
+ },
162
+ sessionConfig: {
163
+ ...(mergedConfig.model ? { model: mergedConfig.model } : {}),
164
+ ...(mergedConfig.permissionMode ? { permissionMode: mergedConfig.permissionMode } : {}),
165
+ ...(mergedConfig.effort ? { effort: mergedConfig.effort } : {}),
166
+ ...(mergedConfig.runtimeControlValues
167
+ ? { runtimeControlValues: mergedConfig.runtimeControlValues }
168
+ : {}),
169
+ ...(mergedConfig.workspaceId ? { workspaceId: mergedConfig.workspaceId } : {}),
170
+ ...(mergedConfig.executionMode ? { executionMode: mergedConfig.executionMode } : {}),
171
+ },
172
+ lastHeartbeatAt: undefined,
173
+ });
174
+ let executionBranch = liveConfig?.executionBranch ?? null;
175
+ if (!executionBranch && snapshot.executionMode === 'worktree' && snapshot.workspaceId) {
176
+ const workspace = input.workspaceOptions.find((option) => option.id === snapshot.workspaceId);
177
+ if (workspace) {
178
+ executionBranch = buildConversationWorktreeSpec({
179
+ agentId: input.agentId,
180
+ conversationId,
181
+ workspaceCwd: workspace.cwd,
182
+ }).branch;
183
+ }
184
+ }
185
+ return patchAgentSessionSnapshot(conversationId, input.agentId, {
186
+ clientType: input.clientType,
187
+ hostMode: true,
188
+ model: snapshot.model ?? null,
189
+ permissionMode: snapshot.permissionMode ?? null,
190
+ effort: snapshot.effort ?? null,
191
+ runtimeControlValues: snapshot.runtimeControlValues ?? null,
192
+ workspaceId: snapshot.workspaceId ?? null,
193
+ executionMode: snapshot.executionMode ?? null,
194
+ executionBranch,
195
+ modelOptions: snapshot.modelOptions,
196
+ permissionModeOptions: snapshot.permissionModeOptions,
197
+ workspaceOptions: snapshot.workspaceOptions,
198
+ availableExecutionModes: snapshot.availableExecutionModes,
199
+ lastHeartbeatAt: { '.sv': 'timestamp' },
200
+ });
201
+ }));
202
+ }
119
203
  export function readHostSessionConfig(raw, extraStringFields = []) {
120
204
  const baseConfig = readSessionWorkspaceConfig(raw);
121
205
  if (!raw || typeof raw !== 'object') {
@@ -126,9 +210,16 @@ export function readHostSessionConfig(raw, extraStringFields = []) {
126
210
  const value = normalizeOptionalString(data[field]);
127
211
  return value ? [[field, value]] : [];
128
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
+ }));
129
219
  return {
130
220
  ...(baseConfig ?? {}),
131
221
  ...extraConfig,
222
+ ...(Object.keys(runtimeControlValues).length > 0 ? { runtimeControlValues } : {}),
132
223
  };
133
224
  }
134
225
  export async function loadHostSessionConfig(input) {
package/dist/host.js CHANGED
@@ -3,8 +3,8 @@ 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 { buildConfiguredWorkspaceOptions, 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, shouldTriggerAgentTurn, writeSessionState, writeTurnState, } from '@canonmsg/core';
7
- import { buildCanonHostPrompt, buildHydratedInboundContext, createConversationMetadataLoader, loadHostSessionConfig, publishHostAgentRuntime, renderCanonHostInboundContent, resolveHostWorkspaceCwd, } from './host-runtime.js';
6
+ import { buildConfiguredWorkspaceOptions, 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
+ 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';
10
10
  import { clearStoredThreadId, loadStoredThreadId, saveStoredThreadId, } from './session-store.js';
@@ -23,6 +23,61 @@ const CODEX_RUNTIME_CAPABILITIES = {
23
23
  };
24
24
  let workingDir = process.cwd();
25
25
  let workspaceOptions = [];
26
+ function buildCodexRuntimeDescriptor(input) {
27
+ return {
28
+ coreControls: [
29
+ {
30
+ id: 'model',
31
+ label: 'Model',
32
+ options: input.models,
33
+ defaultValue: input.models[0]?.value ?? null,
34
+ availability: 'setup_and_live',
35
+ liveBehavior: 'next_turn',
36
+ selectionPolicy: 'inherit',
37
+ },
38
+ {
39
+ id: 'workspace',
40
+ label: 'Workspace',
41
+ options: input.workspaces.map((workspace) => ({
42
+ value: workspace.id,
43
+ label: workspace.label,
44
+ })),
45
+ defaultValue: input.workspaces[0]?.id ?? null,
46
+ availability: 'setup',
47
+ liveBehavior: 'none',
48
+ selectionPolicy: 'inherit',
49
+ },
50
+ {
51
+ id: 'executionMode',
52
+ label: 'Execution mode',
53
+ options: input.executionModes.map((mode) => ({
54
+ value: mode,
55
+ label: mode === 'worktree' ? 'Isolated worktree' : 'Use shared workspace',
56
+ description: mode === 'worktree'
57
+ ? 'Creates or reuses a per-conversation git worktree under ~/.canon/conversation-worktrees when the selected workspace is a git repo.'
58
+ : 'Runs directly in the selected workspace. Canon records usage, but does not create a separate checkout.',
59
+ })),
60
+ defaultValue: null,
61
+ availability: 'setup',
62
+ liveBehavior: 'none',
63
+ selectionPolicy: 'required_explicit',
64
+ },
65
+ ],
66
+ runtimeControls: [
67
+ {
68
+ id: 'permissionMode',
69
+ label: 'Execution policy',
70
+ options: input.permissionModes,
71
+ defaultValue: input.defaultPermissionMode ?? null,
72
+ availability: 'setup',
73
+ liveBehavior: 'none',
74
+ selectionPolicy: 'inherit',
75
+ },
76
+ ],
77
+ supportsInterrupt: true,
78
+ streamingTextMode: 'snapshot',
79
+ };
80
+ }
26
81
  function normalizeRuntimeTurnState(value) {
27
82
  const normalizedTurn = normalizeTurnState(value);
28
83
  if (normalizedTurn) {
@@ -62,6 +117,14 @@ function resolveWorkspaceCwd(config) {
62
117
  defaultCwd: workingDir,
63
118
  });
64
119
  }
120
+ function resolveExecutionFallbackReason(environment) {
121
+ if (!environment?.reason || environment.mode !== 'locked') {
122
+ return null;
123
+ }
124
+ return environment.reason === 'Sharing the base workspace (locked mode)'
125
+ ? null
126
+ : environment.reason;
127
+ }
65
128
  function buildCanonPrompt(input) {
66
129
  return buildCanonHostPrompt({
67
130
  hostLabel: 'Codex',
@@ -138,10 +201,27 @@ export async function main() {
138
201
  const sessions = new Map();
139
202
  const pendingSessionCreations = new Map();
140
203
  const conversationCache = new Map();
204
+ const knownConversationIds = new Set();
205
+ let lastKnownConversationRefreshAt = 0;
141
206
  const { getConversationMeta } = createConversationMetadataLoader({
142
207
  client,
143
208
  conversationCache,
144
209
  });
210
+ function resolveWorkspaceIdForBaseCwd(baseCwd) {
211
+ return workspaceOptions.find((option) => option.cwd === baseCwd)?.id;
212
+ }
213
+ async function refreshKnownConversationIds(force = false) {
214
+ if (!force && Date.now() - lastKnownConversationRefreshAt < HEARTBEAT_MS) {
215
+ return;
216
+ }
217
+ const conversations = await client.getConversations();
218
+ knownConversationIds.clear();
219
+ for (const conversation of conversations) {
220
+ knownConversationIds.add(conversation.id);
221
+ conversationCache.set(conversation.id, conversation);
222
+ }
223
+ lastKnownConversationRefreshAt = Date.now();
224
+ }
145
225
  async function loadSenderRuntimeState(conversationId, senderId) {
146
226
  try {
147
227
  const [turnState, sessionState] = await Promise.all([
@@ -177,6 +257,7 @@ export async function main() {
177
257
  cwd: session.cwd,
178
258
  executionMode: session.environment.mode,
179
259
  ...(session.environment.branch ? { executionBranch: session.environment.branch } : {}),
260
+ ...(session.environment.worktreePath ? { worktreePath: session.environment.worktreePath } : {}),
180
261
  hostMode: true,
181
262
  clientType: 'codex',
182
263
  state: session.state.state,
@@ -236,6 +317,7 @@ export async function main() {
236
317
  }
237
318
  }
238
319
  async function getOrCreateSession(conversationId) {
320
+ knownConversationIds.add(conversationId);
239
321
  const existing = sessions.get(conversationId);
240
322
  if (existing && !existing.closed) {
241
323
  existing.lastActivity = Date.now();
@@ -335,6 +417,7 @@ export async function main() {
335
417
  void runNextTurn(session);
336
418
  }
337
419
  async function enqueueInboundMessage(input) {
420
+ knownConversationIds.add(input.conversationId);
338
421
  let materialized = [];
339
422
  if (input.message.id) {
340
423
  try {
@@ -551,13 +634,88 @@ export async function main() {
551
634
  ...(codexPermissionEnvelope.defaultPermissionMode
552
635
  ? { defaultPermissionMode: codexPermissionEnvelope.defaultPermissionMode }
553
636
  : {}),
637
+ runtimeDescriptor: buildCodexRuntimeDescriptor({
638
+ models: [],
639
+ workspaces: buildPublicWorkspaceOptions(workspaceOptions),
640
+ executionModes: hostAvailableExecutionModes,
641
+ permissionModes: [...codexPermissionEnvelope.availablePermissionModes],
642
+ defaultPermissionMode: codexPermissionEnvelope.defaultPermissionMode,
643
+ }),
554
644
  };
555
645
  const publishRuntimeHeartbeat = async () => {
556
646
  if (!streamConnected)
557
647
  return;
648
+ await refreshKnownConversationIds().catch((error) => {
649
+ console.error('[canon-codex] Failed to refresh known conversations:', error);
650
+ });
558
651
  await publishAgentRuntime(agentId, runtimeDescriptor).catch((error) => {
559
652
  console.error('[canon-codex] Failed to publish agent runtime:', error);
560
653
  });
654
+ await publishHostSessionSnapshots({
655
+ conversationIds: Array.from(knownConversationIds),
656
+ agentId,
657
+ clientType: 'codex',
658
+ runtime: runtimeDescriptor,
659
+ workspaceOptions,
660
+ defaultCwd: workingDir,
661
+ liveSessionConfigByConversation: new Map(Array.from(sessions.values()).map((session) => {
662
+ const workspaceId = resolveWorkspaceIdForBaseCwd(session.environment.baseCwd);
663
+ return [
664
+ session.conversationId,
665
+ {
666
+ ...(session.state.model ? { model: session.state.model } : {}),
667
+ ...(workspaceId ? { workspaceId } : {}),
668
+ executionMode: session.environment.mode,
669
+ executionBranch: session.environment.branch ?? null,
670
+ },
671
+ ];
672
+ })),
673
+ }).catch((error) => {
674
+ console.error('[canon-codex] Failed to publish session snapshots:', error);
675
+ });
676
+ await Promise.all(Array.from(knownConversationIds).map(async (conversationId) => {
677
+ const session = sessions.get(conversationId);
678
+ const workspaceId = session
679
+ ? resolveWorkspaceIdForBaseCwd(session.environment.baseCwd)
680
+ : runtimeDescriptor.defaultWorkspaceId;
681
+ const workspace = workspaceOptions.find((option) => option.id === workspaceId) ?? null;
682
+ const payload = {
683
+ descriptor: runtimeDescriptor.runtimeDescriptor ?? buildCodexRuntimeDescriptor({
684
+ models: runtimeDescriptor.availableModels ?? [],
685
+ workspaces: buildPublicWorkspaceOptions(workspaceOptions),
686
+ executionModes: hostAvailableExecutionModes,
687
+ permissionModes: [...codexPermissionEnvelope.availablePermissionModes],
688
+ defaultPermissionMode: codexPermissionEnvelope.defaultPermissionMode,
689
+ }),
690
+ surfaceMode: 'host',
691
+ statusItems: [
692
+ {
693
+ id: 'transport',
694
+ label: 'Transport',
695
+ value: 'exec --json',
696
+ },
697
+ {
698
+ id: 'streaming',
699
+ label: 'Live output',
700
+ value: 'Thinking, tools, and completed-message previews',
701
+ },
702
+ ],
703
+ execution: {
704
+ resolvedWorkspaceLabel: workspace?.label ?? workspaceId ?? null,
705
+ resolvedCwd: session?.cwd ?? workspace?.cwd ?? workingDir,
706
+ executionMode: session?.environment.mode ?? null,
707
+ executionBranch: session?.environment.branch ?? null,
708
+ worktreePath: session?.environment.worktreePath ?? null,
709
+ fallbackReason: resolveExecutionFallbackReason(session?.environment),
710
+ },
711
+ notes: [
712
+ '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.',
713
+ ],
714
+ };
715
+ await writeRuntimeInfo(conversationId, agentId, payload);
716
+ })).catch((error) => {
717
+ console.error('[canon-codex] Failed to publish runtime info:', error);
718
+ });
561
719
  };
562
720
  const stream = new CanonStream({
563
721
  apiKey,
@@ -599,6 +757,13 @@ export async function main() {
599
757
  ...(codexPermissionEnvelope.defaultPermissionMode
600
758
  ? { defaultPermissionMode: codexPermissionEnvelope.defaultPermissionMode }
601
759
  : {}),
760
+ runtimeDescriptor: buildCodexRuntimeDescriptor({
761
+ models: [],
762
+ workspaces: buildPublicWorkspaceOptions(workspaceOptions),
763
+ executionModes: hostAvailableExecutionModes,
764
+ permissionModes: [...codexPermissionEnvelope.availablePermissionModes],
765
+ defaultPermissionMode: codexPermissionEnvelope.defaultPermissionMode,
766
+ }),
602
767
  };
603
768
  }
604
769
  catch {
@@ -610,11 +775,21 @@ export async function main() {
610
775
  ...(codexPermissionEnvelope.defaultPermissionMode
611
776
  ? { defaultPermissionMode: codexPermissionEnvelope.defaultPermissionMode }
612
777
  : {}),
778
+ runtimeDescriptor: buildCodexRuntimeDescriptor({
779
+ models: [],
780
+ workspaces: buildPublicWorkspaceOptions(workspaceOptions),
781
+ executionModes: hostAvailableExecutionModes,
782
+ permissionModes: [...codexPermissionEnvelope.availablePermissionModes],
783
+ defaultPermissionMode: codexPermissionEnvelope.defaultPermissionMode,
784
+ }),
613
785
  };
614
786
  }
615
787
  try {
616
788
  const conversations = await client.getConversations();
789
+ lastKnownConversationRefreshAt = Date.now();
617
790
  for (const conversation of conversations) {
791
+ knownConversationIds.add(conversation.id);
792
+ conversationCache.set(conversation.id, conversation);
618
793
  clearStreaming(conversation.id);
619
794
  clearSessionState(conversation.id, agentId).catch(() => { });
620
795
  clearTurnState(conversation.id, agentId).catch(() => { });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@canonmsg/codex-plugin",
3
- "version": "0.6.5",
3
+ "version": "0.7.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.8.2",
33
- "@canonmsg/core": "^0.7.4"
32
+ "@canonmsg/agent-sdk": "^0.9.0",
33
+ "@canonmsg/core": "^0.8.0"
34
34
  },
35
35
  "engines": {
36
36
  "node": ">=18.0.0"