@canonmsg/codex-plugin 0.9.7 → 0.9.8

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
@@ -64,7 +64,8 @@ export declare class CodexConversationAdapter {
64
64
  setModel(model: string | null): void;
65
65
  isRunning(): boolean;
66
66
  interrupt(): Promise<void>;
67
- runTurn(prompt: string, onEvent: (event: CodexEvent) => void, onLog?: (line: string) => void, imagePaths?: readonly string[]): Promise<CodexTurnResult>;
67
+ runTurn(prompt: string, onEvent: (event: CodexEvent) => void, onLog?: (line: string) => void, imagePaths?: readonly string[], extraAddDirs?: readonly string[]): Promise<CodexTurnResult>;
68
+ private buildAddDirs;
68
69
  private buildArgs;
69
70
  private canResumeWithCurrentPolicy;
70
71
  private clearActiveProcess;
package/dist/adapter.js CHANGED
@@ -51,11 +51,11 @@ export class CodexConversationAdapter {
51
51
  this.child.kill('SIGKILL');
52
52
  }, 5_000);
53
53
  }
54
- async runTurn(prompt, onEvent, onLog, imagePaths = []) {
54
+ async runTurn(prompt, onEvent, onLog, imagePaths = [], extraAddDirs = []) {
55
55
  if (this.child) {
56
56
  throw new Error('A Codex turn is already in progress for this conversation');
57
57
  }
58
- const args = this.buildArgs(prompt, imagePaths);
58
+ const args = this.buildArgs(prompt, imagePaths, extraAddDirs);
59
59
  const child = spawn(this.codexBin, args, {
60
60
  cwd: this.cwd,
61
61
  stdio: ['ignore', 'pipe', 'pipe'],
@@ -147,7 +147,19 @@ export class CodexConversationAdapter {
147
147
  });
148
148
  });
149
149
  }
150
- buildArgs(prompt, imagePaths = []) {
150
+ buildAddDirs(extraAddDirs = []) {
151
+ const seen = new Set();
152
+ const dirs = [];
153
+ for (const value of [...this.addDirs, ...extraAddDirs]) {
154
+ const trimmed = value.trim();
155
+ if (!trimmed || seen.has(trimmed))
156
+ continue;
157
+ seen.add(trimmed);
158
+ dirs.push(value);
159
+ }
160
+ return dirs;
161
+ }
162
+ buildArgs(prompt, imagePaths = [], extraAddDirs = []) {
151
163
  if (this.threadId && this.canResumeWithCurrentPolicy()) {
152
164
  const args = ['exec', 'resume', '--json', '--skip-git-repo-check'];
153
165
  if (this.model) {
@@ -159,6 +171,9 @@ export class CodexConversationAdapter {
159
171
  for (const configOverride of this.configOverrides) {
160
172
  args.push('-c', configOverride);
161
173
  }
174
+ for (const addDir of this.buildAddDirs(extraAddDirs)) {
175
+ args.push('--add-dir', addDir);
176
+ }
162
177
  if (this.fullAuto) {
163
178
  args.push('--full-auto');
164
179
  }
@@ -168,6 +183,9 @@ export class CodexConversationAdapter {
168
183
  for (const imagePath of imagePaths) {
169
184
  args.push('-i', imagePath);
170
185
  }
186
+ if (imagePaths.length > 0) {
187
+ args.push('--');
188
+ }
171
189
  args.push(this.threadId, prompt);
172
190
  return args;
173
191
  }
@@ -189,7 +207,7 @@ export class CodexConversationAdapter {
189
207
  if (this.codexProfile) {
190
208
  args.push('-p', this.codexProfile);
191
209
  }
192
- for (const addDir of this.addDirs) {
210
+ for (const addDir of this.buildAddDirs(extraAddDirs)) {
193
211
  args.push('--add-dir', addDir);
194
212
  }
195
213
  for (const configOverride of this.configOverrides) {
@@ -204,6 +222,9 @@ export class CodexConversationAdapter {
204
222
  for (const imagePath of imagePaths) {
205
223
  args.push('-i', imagePath);
206
224
  }
225
+ if (imagePaths.length > 0) {
226
+ args.push('--');
227
+ }
207
228
  args.push(prompt);
208
229
  return args;
209
230
  }
package/dist/host.js CHANGED
@@ -1,6 +1,7 @@
1
1
  #!/usr/bin/env node
2
2
  import { setDefaultResultOrder } from 'node:dns';
3
3
  import { randomUUID } from 'node:crypto';
4
+ import { dirname } from 'node:path';
4
5
  import { parseArgs } from 'node:util';
5
6
  import { getCodexImagePath, materializeMessageMedia, } from '@canonmsg/agent-sdk';
6
7
  import { buildCanonHostPrompt, buildConfiguredWorkspaceOptionsWithRoots, buildFirstPartyCodingRuntimeDescriptor, buildHydratedInboundContext, buildPublicWorkspaceRoots, buildPublicWorkspaceOptions, createConversationMetadataLoader, createRuntimeStatePublisher, 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, normalizeTurnState, prepareConversationEnvironment, loadHostSessionConfig, releaseConversationEnvironment, resolveCanonAgent, rtdbRead, rtdbWrite, shouldTriggerAgentTurn, saveRuntimeSessionState, publishHostAgentRuntime, publishHostSessionSnapshots, renderCanonHostInboundContent, resolveHostWorkspaceCwd, upsertLocalRuntimeEntry, } from '@canonmsg/core';
@@ -207,6 +208,9 @@ function summarizeCommand(command) {
207
208
  function sleep(ms) {
208
209
  return new Promise((resolve) => setTimeout(resolve, ms));
209
210
  }
211
+ function uniqueStrings(values) {
212
+ return Array.from(new Set(values.filter((value) => value.trim().length > 0)));
213
+ }
210
214
  export async function main() {
211
215
  setDefaultResultOrder('ipv4first');
212
216
  const { values: args } = parseArgs({
@@ -525,6 +529,7 @@ export async function main() {
525
529
  turnState: 'idle',
526
530
  currentTurnId: null,
527
531
  currentTurnOpenedAt: null,
532
+ activeSelfContextId: null,
528
533
  lastAcceptedIntent: null,
529
534
  lastActivity: Date.now(),
530
535
  typingKeepaliveTimer: null,
@@ -553,8 +558,8 @@ export async function main() {
553
558
  pendingSessionCreations.delete(conversationId);
554
559
  }
555
560
  }
556
- function enqueuePrompt(session, prompt, intent = 'queue', toFront = false, sourceMessageId, markAccepted = false, imagePaths = []) {
557
- const nextPrompt = { prompt, intent, sourceMessageId, markAccepted, imagePaths };
561
+ function enqueuePrompt(session, prompt, intent = 'queue', toFront = false, sourceMessageId, markAccepted = false, imagePaths = [], mediaAddDirs = []) {
562
+ const nextPrompt = { prompt, intent, sourceMessageId, markAccepted, imagePaths, mediaAddDirs };
558
563
  if (toFront) {
559
564
  session.queue.unshift(nextPrompt);
560
565
  }
@@ -582,6 +587,7 @@ export async function main() {
582
587
  const imagePaths = materialized
583
588
  .map((attachment) => getCodexImagePath(attachment))
584
589
  .filter((path) => path !== null);
590
+ const mediaAddDirs = uniqueStrings(materialized.map((attachment) => dirname(attachment.path)));
585
591
  const content = renderInboundContent(input.message, materialized);
586
592
  const hydrated = await loadHydratedInboundContext({
587
593
  conversationId: input.conversationId,
@@ -591,11 +597,11 @@ export async function main() {
591
597
  hydratedPage: input.hydratedPage,
592
598
  });
593
599
  const behavior = input.behavior ?? hydrated.behavior;
594
- const workSessions = hydrated.hydratedFromPage
595
- ? hydrated.workSessions
596
- : Array.isArray(input.workSessions)
597
- ? input.workSessions
598
- : hydrated.workSessions;
600
+ const selfContexts = hydrated.hydratedFromPage
601
+ ? hydrated.selfContexts
602
+ : Array.isArray(input.selfContexts)
603
+ ? input.selfContexts
604
+ : hydrated.selfContexts;
599
605
  const participantContext = hydrated.participantContext;
600
606
  const autoReply = decideAutoReply(participantContext, behavior);
601
607
  if (!autoReply.allow) {
@@ -612,6 +618,7 @@ export async function main() {
612
618
  const userMessage = error instanceof ExecutionEnvironmentError ? error.userMessage : message;
613
619
  console.error(`[canon-codex] [${input.conversationId.slice(0, 8)}] Failed to create session: ${message}`);
614
620
  await client.sendMessage(input.conversationId, `I couldn't start a coding session for this workspace: ${userMessage}`, {
621
+ ...(selfContexts[0]?.id ? { selfContextId: selfContexts[0].id } : {}),
615
622
  metadata: {
616
623
  turnSemantics: 'turn_complete',
617
624
  turnComplete: true,
@@ -621,6 +628,7 @@ export async function main() {
621
628
  return;
622
629
  }
623
630
  const turnMetadata = normalizeTurnMetadata(input.message.metadata);
631
+ session.activeSelfContextId = selfContexts[0]?.id ?? null;
624
632
  const deliveryIntent = turnMetadata?.deliveryIntent ?? 'queue';
625
633
  const shouldMarkAccepted = turnMetadata?.inboundDisposition === 'queued';
626
634
  const prompt = buildCanonPrompt({
@@ -628,18 +636,17 @@ export async function main() {
628
636
  conversationId: input.conversationId,
629
637
  participantContext,
630
638
  behavior,
631
- workSession: input.message.workSession,
632
- workSessions,
639
+ selfContexts,
633
640
  });
634
641
  if (session.running && deliveryIntent === 'interrupt') {
635
- enqueuePrompt(session, prompt, deliveryIntent, true, input.message.id, shouldMarkAccepted, imagePaths);
642
+ enqueuePrompt(session, prompt, deliveryIntent, true, input.message.id, shouldMarkAccepted, imagePaths, mediaAddDirs);
636
643
  console.error(`[canon-codex] [${input.conversationId.slice(0, 8)}] Interrupting current turn for explicit human send-now`);
637
644
  await session.adapter.interrupt().catch(() => { });
638
645
  clearStreaming(input.conversationId);
639
646
  client.setTyping(input.conversationId, false).catch(() => { });
640
647
  return;
641
648
  }
642
- enqueuePrompt(session, prompt, deliveryIntent, false, input.message.id, shouldMarkAccepted, imagePaths);
649
+ enqueuePrompt(session, prompt, deliveryIntent, false, input.message.id, shouldMarkAccepted, imagePaths, mediaAddDirs);
643
650
  }
644
651
  async function runNextTurn(session) {
645
652
  if (session.running || session.closed)
@@ -669,6 +676,7 @@ export async function main() {
669
676
  throw new ExecutionEnvironmentError(modelGuard, modelGuard);
670
677
  }
671
678
  const turnImagePaths = nextTurn.imagePaths ?? [];
679
+ const turnMediaAddDirs = nextTurn.mediaAddDirs ?? [];
672
680
  const handleCodexEvent = (event) => {
673
681
  session.lastActivity = Date.now();
674
682
  if (event.type === 'thread.started') {
@@ -708,7 +716,7 @@ export async function main() {
708
716
  clearStoredThreadId(runtimeId, agentId, session.conversationId, session.environment.baseCwd, session.environment.mode);
709
717
  session.adapter.clearThreadId();
710
718
  };
711
- const runTurnOnce = () => session.adapter.runTurn(nextTurn.prompt, handleCodexEvent, logCodexLine, turnImagePaths);
719
+ const runTurnOnce = () => session.adapter.runTurn(nextTurn.prompt, handleCodexEvent, logCodexLine, turnImagePaths, turnMediaAddDirs);
712
720
  let result = await runTurnOnce();
713
721
  if (!result.interrupted
714
722
  && !result.finalMessage
@@ -727,6 +735,9 @@ export async function main() {
727
735
  clearStoredThread();
728
736
  }
729
737
  await client.sendMessage(session.conversationId, result.finalMessage, {
738
+ ...(session.activeSelfContextId
739
+ ? { selfContextId: session.activeSelfContextId }
740
+ : {}),
730
741
  metadata: {
731
742
  turnId: session.currentTurnId,
732
743
  turnSemantics: 'turn_complete',
@@ -745,6 +756,9 @@ export async function main() {
745
756
  console.error(`[canon-codex] [${session.conversationId.slice(0, 8)}] Turn exited ${result.exitCode}: ${result.errorText}`);
746
757
  }
747
758
  await client.sendMessage(session.conversationId, userVisibleError, {
759
+ ...(session.activeSelfContextId
760
+ ? { selfContextId: session.activeSelfContextId }
761
+ : {}),
748
762
  metadata: {
749
763
  turnId: session.currentTurnId,
750
764
  turnSemantics: 'turn_complete',
@@ -773,6 +787,9 @@ export async function main() {
773
787
  session.state.lastError = message;
774
788
  writeState(session);
775
789
  await client.sendMessage(session.conversationId, message, {
790
+ ...(session.activeSelfContextId
791
+ ? { selfContextId: session.activeSelfContextId }
792
+ : {}),
776
793
  metadata: {
777
794
  turnId: session.currentTurnId,
778
795
  turnSemantics: 'turn_complete',
@@ -969,7 +986,7 @@ export async function main() {
969
986
  senderName: message.senderName || message.senderId,
970
987
  isOwner: message.isOwner ?? (ownerId != null && message.senderId === ownerId),
971
988
  behavior: payload.behavior,
972
- workSessions: payload.workSessions,
989
+ selfContexts: payload.selfContexts,
973
990
  });
974
991
  if (message.id) {
975
992
  saveRuntimeSessionState(runtimeId, {
@@ -1076,7 +1093,7 @@ export async function main() {
1076
1093
  senderName: latestMessage.senderId,
1077
1094
  isOwner: ownerId != null && latestMessage.senderId === ownerId,
1078
1095
  behavior: latestPage.behavior,
1079
- workSessions: latestPage.workSessions,
1096
+ selfContexts: latestPage.selfContexts,
1080
1097
  hydratedPage: latestPage,
1081
1098
  });
1082
1099
  if (latestMessage.id) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@canonmsg/codex-plugin",
3
- "version": "0.9.7",
3
+ "version": "0.9.8",
4
4
  "description": "Canon host integration for Codex CLI",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
@@ -29,7 +29,7 @@
29
29
  "prepack": "npm run build"
30
30
  },
31
31
  "dependencies": {
32
- "@canonmsg/agent-sdk": "^1.1.2",
32
+ "@canonmsg/agent-sdk": "^1.1.4",
33
33
  "@canonmsg/core": "^0.15.4"
34
34
  },
35
35
  "engines": {