@canonmsg/codex-plugin 0.30.1 → 0.31.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/dist/host.d.ts CHANGED
@@ -247,6 +247,11 @@ export declare function shouldRouteCodexTurnArtifacts(input: {
247
247
  artifactRoutingMode: TurnArtifactRoutingMode | undefined;
248
248
  silenced: boolean;
249
249
  finalText: string | null | undefined;
250
+ currentAudience?: {
251
+ memberIds?: readonly string[];
252
+ agentId: string;
253
+ ownerId: string | null | undefined;
254
+ };
250
255
  }): TurnArtifactRoutingDecision;
251
256
  export declare function main(): Promise<void>;
252
257
  export {};
package/dist/host.js CHANGED
@@ -6,7 +6,7 @@ import { dirname } from 'node:path';
6
6
  import { parseArgs } from 'node:util';
7
7
  import { getCodexImagePath, materializeMessageMedia, materializeReplyContextMedia, sendMediaFileMessage, } from '@canonmsg/agent-sdk';
8
8
  import { buildTrailBlockId, buildUndeliverableFinalNotice, captureTurnArtifactSnapshot, createTurnArtifactRouter, IDLE_TIMEOUT_MS, PLAN_BLOCK_TITLE, resolveTurnArtifactRouting, collectMissedInboundMessages, createRecoveryCheckpointTracker, createReconnectRecoveryCoordinator, STARTUP_RECOVERY_PAGE_SIZE, } from '@canonmsg/coding-agent-host';
9
- import { RUNTIME_NEW_SESSION_ACTION, RUNTIME_STOP_ACTION, RUNTIME_STOP_AND_DROP_ACTION, buildRuntimeCardOutcome, resolveQuestionAllowOther, buildCanonTurnContextV2, buildConfiguredWorkspaceOptionsWithRoots, buildFirstPartyCodingRuntimeDescriptor, buildHydratedInboundContext, diffCanonMemberIds, buildPublicWorkspaceRoots, buildPublicWorkspaceOptions, buildRuntimePresentationPolicy, buildCanonInboundFrameV1, DEFAULT_FIRST_PARTY_RUNTIME_PRESENTATION, createConversationMetadataLoader, createRuntimeStatePublisher, createRuntimeHeartbeat, createTypingStatusPublisher, EXECUTION_ENVIRONMENT_MODES, ExecutionEnvironmentError, CanonClient, CanonStream, DEFAULT_PARTICIPATION_HISTORY_FETCH_LIMIT, DEFAULT_RUNTIME_CAPABILITIES, FINAL_MESSAGE_HANDOFF_MS, getActiveProfileLock, decideAutoReply, initRTDBAuth, buildLocalRuntimeId, heartbeatLocalRuntimeEntry, markLocalRuntimeStopped, normalizeTurnMetadata, parseTurnVerbosityConfig, RuntimeRequestManager, prepareConversationEnvironment, releaseConversationEnvironment, resolveLocalRuntimeSessionState, resolveCanonAgent, verifyResolvedAgentEnvironment, CanonApiError, loadRuntimeSessionState, sendMessageWithRetry, sendMessageWithRetryChunked, saveRuntimeSessionState, publishHostSessionSnapshots, renderCanonHostInboundContent, renderCodingHostInboundPrompt, resolveConfiguredWorkspaceCwd, isSilentTurnSuppressed, resolveSilentTurnDelivery, resolveTurnVerbosity, shouldTriggerAgentTurn, upsertLocalRuntimeEntry, } from '@canonmsg/core';
9
+ import { RUNTIME_NEW_SESSION_ACTION, RUNTIME_STOP_ACTION, RUNTIME_STOP_AND_DROP_ACTION, buildRuntimeCardOutcome, resolveQuestionAllowOther, buildCanonTurnContextV2, buildCanonGroupContext, buildCompactGroupContextLines, buildConfiguredWorkspaceOptionsWithRoots, buildFirstPartyCodingRuntimeDescriptor, buildHydratedInboundContext, diffCanonMemberIds, buildPublicWorkspaceRoots, buildPublicWorkspaceOptions, buildRuntimePresentationPolicy, buildCanonInboundFrameV1, DEFAULT_FIRST_PARTY_RUNTIME_PRESENTATION, createConversationMetadataLoader, createRuntimeStatePublisher, createRuntimeHeartbeat, createTypingStatusPublisher, EXECUTION_ENVIRONMENT_MODES, ExecutionEnvironmentError, CanonClient, CanonStream, DEFAULT_PARTICIPATION_HISTORY_FETCH_LIMIT, DEFAULT_RUNTIME_CAPABILITIES, FINAL_MESSAGE_HANDOFF_MS, getActiveProfileLock, decideAutoReply, initRTDBAuth, buildLocalRuntimeId, heartbeatLocalRuntimeEntry, markLocalRuntimeStopped, normalizeTurnMetadata, parseTurnVerbosityConfig, RuntimeRequestManager, prepareConversationEnvironment, releaseConversationEnvironment, resolveLocalRuntimeSessionState, resolveCanonAgent, verifyResolvedAgentEnvironment, CanonApiError, loadRuntimeSessionState, sendMessageWithRetry, sendMessageWithRetryChunked, saveRuntimeSessionState, publishHostSessionSnapshots, renderCanonHostInboundContent, renderCodingHostInboundPrompt, resolveConfiguredWorkspaceCwd, isSilentTurnSuppressed, resolveSilentTurnDelivery, resolveTurnVerbosity, shouldTriggerAgentTurn, upsertLocalRuntimeEntry, } from '@canonmsg/core';
10
10
  import { validateCard } from '@canonmsg/rich-cards';
11
11
  import { CodexConversationAdapter, } from './adapter.js';
12
12
  import { CodexAppServerAdapter, } from './app-server-adapter.js';
@@ -599,8 +599,12 @@ export function selectCodexNativeImagePaths(imagePaths, nativeVisionEnabled) {
599
599
  * does not currently carry into the catch without a race.
600
600
  */
601
601
  export function shouldRouteCodexTurnArtifacts(input) {
602
+ const audience = input.currentAudience;
603
+ const ownerPair = !audience || (audience.ownerId !== audience.agentId
604
+ && Boolean(audience.ownerId) && audience.memberIds?.length === 2
605
+ && audience.memberIds.includes(audience.agentId) && audience.memberIds.includes(audience.ownerId));
602
606
  return resolveTurnArtifactRouting({
603
- artifactRoutingMode: input.artifactRoutingMode,
607
+ artifactRoutingMode: ownerPair ? input.artifactRoutingMode : 'disabled',
604
608
  silenced: isSilentTurnSuppressed({ silenced: input.silenced, finalText: input.finalText }),
605
609
  });
606
610
  }
@@ -854,6 +858,7 @@ export async function main() {
854
858
  if (cached) {
855
859
  conversationCache.set(payload.conversationId, {
856
860
  ...cached,
861
+ ...payload.changes,
857
862
  memberIds,
858
863
  });
859
864
  }
@@ -862,11 +867,12 @@ export async function main() {
862
867
  }
863
868
  if (!memberIds.includes(agentId)) {
864
869
  knownConversationIds.delete(payload.conversationId);
865
- conversationCache.delete(payload.conversationId);
870
+ pendingMembershipChanges.delete(payload.conversationId);
871
+ closeSession(payload.conversationId);
866
872
  }
867
873
  }
868
874
  function getGroupContextMode(conversationId, conversation) {
869
- if (conversation?.type !== 'group')
875
+ if (!conversation)
870
876
  return undefined;
871
877
  if (pendingMembershipChanges.has(conversationId))
872
878
  return 'membership_change';
@@ -884,7 +890,7 @@ export async function main() {
884
890
  }
885
891
  async function loadHydratedInboundContext(input) {
886
892
  const [conversation, page] = await Promise.all([
887
- getConversationMeta(input.conversationId),
893
+ getConversationMeta(input.conversationId, { refreshIfMemberMissing: agentId }),
888
894
  input.hydratedPage
889
895
  ? Promise.resolve(input.hydratedPage)
890
896
  : client.getMessagesPage(input.conversationId, DEFAULT_PARTICIPATION_HISTORY_FETCH_LIMIT).catch(() => null),
@@ -893,6 +899,7 @@ export async function main() {
893
899
  agentId,
894
900
  conversationId: input.conversationId,
895
901
  conversation,
902
+ requireAgentMembership: true,
896
903
  page,
897
904
  activeSelfContextId: input.activeSelfContextId,
898
905
  selfContexts: input.selfContexts,
@@ -1204,6 +1211,10 @@ export async function main() {
1204
1211
  }
1205
1212
  }
1206
1213
  async function getOrCreateSession(conversationId) {
1214
+ const current = conversationCache.get(conversationId);
1215
+ if (current && !current.memberIds.includes(agentId)) {
1216
+ throw new Error('Agent is no longer a member of this conversation');
1217
+ }
1207
1218
  knownConversationIds.add(conversationId);
1208
1219
  const existing = sessions.get(conversationId);
1209
1220
  if (existing && !existing.closed) {
@@ -1930,8 +1941,9 @@ export async function main() {
1930
1941
  replyAuthority: input.replyAuthority ?? null,
1931
1942
  });
1932
1943
  }
1933
- function sendTurnArtifactFile(session, file) {
1944
+ function sendTurnArtifactFile(session, file, canPublish) {
1934
1945
  return sendMediaFileMessage(client, session.conversationId, file.path, '', {
1946
+ canPublish,
1935
1947
  ...(session.currentReplyAuthority ? { replyAuthority: session.currentReplyAuthority } : {}),
1936
1948
  ...(session.activeSelfContextId ? { selfContextId: session.activeSelfContextId } : {}),
1937
1949
  metadata: {
@@ -2001,22 +2013,29 @@ export async function main() {
2001
2013
  // the collect-and-post loop, so there is no un-gated path left to the
2002
2014
  // workspace: the failure branches still send Canon's notice, and none of
2003
2015
  // them can post the artifacts of a turn that chose silence.
2016
+ const decideArtifactRouting = () => shouldRouteCodexTurnArtifacts({
2017
+ artifactRoutingMode: nextTurn.artifactRoutingMode,
2018
+ silenced: session.currentTurnSilenced,
2019
+ finalText: turnFinalText,
2020
+ currentAudience: { memberIds: conversationCache.get(session.conversationId)?.memberIds, agentId, ownerId },
2021
+ });
2004
2022
  const artifactRouter = createTurnArtifactRouter({
2005
- decide: () => shouldRouteCodexTurnArtifacts({
2006
- artifactRoutingMode: nextTurn.artifactRoutingMode,
2007
- silenced: session.currentTurnSilenced,
2008
- finalText: turnFinalText,
2009
- }),
2023
+ decide: decideArtifactRouting,
2010
2024
  baseline: () => artifactBaseline,
2011
2025
  cwd: () => session.cwd,
2012
- send: (file) => sendTurnArtifactFile(session, file),
2026
+ send: (file) => sendTurnArtifactFile(session, file, () => decideArtifactRouting().route),
2013
2027
  log: (line) => console.error(`[canon-codex] [${session.conversationId.slice(0, 8)}] ${line}`),
2014
2028
  });
2015
2029
  const routeArtifactsOnce = artifactRouter.route;
2016
2030
  try {
2017
2031
  const turnId = session.currentTurnId ?? randomUUID();
2018
2032
  session.currentTurnId = turnId;
2019
- let turnPrompt = nextTurn.prompt;
2033
+ // Queueing does not freeze the audience: refresh at the native turn
2034
+ // boundary while retaining the same thread and its existing memory.
2035
+ const roster = buildCanonGroupContext({
2036
+ conversation: conversationCache.get(session.conversationId), messages: [], agentId, ownerId, ownerName,
2037
+ });
2038
+ let turnPrompt = [nextTurn.prompt, ...(roster ? buildCompactGroupContextLines(roster, 'initial') : [])].join('\n\n');
2020
2039
  if (nextTurn.artifactRoutingMode === 'workspace-generated') {
2021
2040
  artifactBaseline = await captureTurnArtifactSnapshot({ cwd: session.cwd }).catch((error) => {
2022
2041
  console.error(`[canon-codex] [${session.conversationId.slice(0, 8)}] Artifact snapshot failed:`, error instanceof Error ? error.message : error);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@canonmsg/codex-plugin",
3
- "version": "0.30.1",
3
+ "version": "0.31.0",
4
4
  "description": "Canon host integration for Codex CLI",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
@@ -31,10 +31,10 @@
31
31
  "prepack": "npm run build"
32
32
  },
33
33
  "dependencies": {
34
- "@canonmsg/agent-sdk": "^10.3.0",
35
- "@canonmsg/agent-tools": "^0.9.1",
36
- "@canonmsg/coding-agent-host": "^0.7.1",
37
- "@canonmsg/core": "^12.4.0",
34
+ "@canonmsg/agent-sdk": "^10.4.0",
35
+ "@canonmsg/agent-tools": "^0.10.0",
36
+ "@canonmsg/coding-agent-host": "^0.8.0",
37
+ "@canonmsg/core": "^12.5.0",
38
38
  "@canonmsg/rich-cards": "^0.10.5",
39
39
  "ws": "^8.21.3"
40
40
  },