@canonmsg/codex-plugin 0.9.2 → 0.9.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.
Files changed (2) hide show
  1. package/dist/host.js +37 -28
  2. package/package.json +3 -3
package/dist/host.js CHANGED
@@ -3,7 +3,7 @@ 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 { 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, shouldTriggerAgentTurn, saveRuntimeSessionState, publishHostAgentRuntime, publishHostSessionSnapshots, renderCanonHostInboundContent, resolveHostWorkspaceCwd, upsertLocalRuntimeEntry, } from '@canonmsg/core';
6
+ 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';
7
7
  import { buildInboundContextLines, decideAutoReply, } from './inbound-policy.js';
8
8
  import { CodexConversationAdapter, } from './adapter.js';
9
9
  import { clearStoredThreadId, loadStoredThreadId, saveStoredThreadId, } from './session-store.js';
@@ -63,15 +63,6 @@ function normalizeRuntimeTurnState(value) {
63
63
  if (normalizedTurn) {
64
64
  return { state: normalizedTurn.state };
65
65
  }
66
- if (!value || typeof value !== 'object')
67
- return null;
68
- const state = value.state;
69
- if (state === 'running') {
70
- return { state: 'streaming' };
71
- }
72
- if (state === 'requires_action') {
73
- return { state: 'waiting_input' };
74
- }
75
66
  return null;
76
67
  }
77
68
  async function publishAgentRuntime(agentId, runtime) {
@@ -247,11 +238,7 @@ export async function main() {
247
238
  }
248
239
  async function loadSenderRuntimeState(conversationId, senderId) {
249
240
  try {
250
- const [turnState, sessionState] = await Promise.all([
251
- rtdbRead(`/turn-state/${conversationId}/${senderId}`),
252
- rtdbRead(`/session-state/${conversationId}/${senderId}`),
253
- ]);
254
- return normalizeRuntimeTurnState(turnState) ?? normalizeRuntimeTurnState(sessionState);
241
+ return normalizeRuntimeTurnState(await rtdbRead(`/turn-state/${conversationId}/${senderId}`));
255
242
  }
256
243
  catch {
257
244
  return null;
@@ -286,7 +273,6 @@ export async function main() {
286
273
  : {}),
287
274
  hostMode: true,
288
275
  clientType: 'codex',
289
- state: session.state.state,
290
276
  isActive: true,
291
277
  }).catch(() => { });
292
278
  }
@@ -309,6 +295,13 @@ export async function main() {
309
295
  return;
310
296
  await client.updateMessageDisposition(conversationId, sourceMessageId, 'accepted_now').catch(() => { });
311
297
  }
298
+ async function markQueuedPromptsRejected(conversationId, prompts) {
299
+ await Promise.all(prompts.map((prompt) => {
300
+ if (!prompt.markAccepted || !prompt.sourceMessageId)
301
+ return Promise.resolve();
302
+ return client.updateMessageDisposition(conversationId, prompt.sourceMessageId, 'rejected').catch(() => { });
303
+ }));
304
+ }
312
305
  function clearStreaming(conversationId) {
313
306
  runtimeState.clearStreaming(conversationId).catch(() => { });
314
307
  }
@@ -435,6 +428,7 @@ export async function main() {
435
428
  closed: false,
436
429
  };
437
430
  sessions.set(conversationId, session);
431
+ await baselineControlSignal(conversationId);
438
432
  console.error(`[canon-codex] [${conversationId.slice(0, 8)}] Environment → ${environment.mode} (${sessionCwd})`);
439
433
  writeState(session);
440
434
  writeTurn(session);
@@ -677,6 +671,8 @@ export async function main() {
677
671
  }
678
672
  }
679
673
  let controlStopped = false;
674
+ const lastSeenControl = new Map();
675
+ const lastSeenSignal = new Map();
680
676
  let streamConnected = false;
681
677
  const hostAvailableExecutionModes = [
682
678
  ...EXECUTION_ENVIRONMENT_MODES,
@@ -700,6 +696,17 @@ export async function main() {
700
696
  defaultPermissionMode: codexPermissionEnvelope.defaultPermissionMode,
701
697
  }),
702
698
  };
699
+ async function baselineControlSignal(conversationId) {
700
+ if (lastSeenSignal.has(conversationId))
701
+ return;
702
+ const raw = await rtdbRead(`/control/${conversationId}/${agentId}/signal`).catch(() => null);
703
+ if (!raw || typeof raw !== 'object')
704
+ return;
705
+ const timestamp = Number(raw.updatedAt ?? 0);
706
+ if (timestamp > 0) {
707
+ lastSeenSignal.set(conversationId, timestamp);
708
+ }
709
+ }
703
710
  const publishRuntimeHeartbeat = async () => {
704
711
  heartbeatLocalRuntimeEntry(runtimeId, {
705
712
  agentId,
@@ -744,15 +751,11 @@ export async function main() {
744
751
  ? resolveWorkspaceIdForBaseCwd(session.environment.baseCwd)
745
752
  : runtimeDescriptor.defaultWorkspaceId;
746
753
  const workspace = workspaceOptions.find((option) => option.id === workspaceId) ?? null;
754
+ const descriptor = runtimeDescriptor.runtimeDescriptor;
755
+ if (!descriptor)
756
+ return;
747
757
  const payload = {
748
- descriptor: runtimeDescriptor.runtimeDescriptor ?? buildCodexRuntimeDescriptor({
749
- models: runtimeDescriptor.availableModels ?? [],
750
- workspaces: buildPublicWorkspaceOptions(workspaceOptions),
751
- workspaceRoots: workspaceRootMetadata,
752
- executionModes: hostAvailableExecutionModes,
753
- permissionModes: [...codexPermissionEnvelope.availablePermissionModes],
754
- defaultPermissionMode: codexPermissionEnvelope.defaultPermissionMode,
755
- }),
758
+ descriptor,
756
759
  surfaceMode: 'host',
757
760
  statusItems: [
758
761
  {
@@ -932,8 +935,6 @@ export async function main() {
932
935
  await stream.start().catch((error) => {
933
936
  console.error('[canon-codex] SSE start error:', error instanceof Error ? error.message : error);
934
937
  });
935
- const lastSeenControl = new Map();
936
- const lastSeenSignal = new Map();
937
938
  const pollControl = async () => {
938
939
  while (!controlStopped) {
939
940
  for (const conversationId of [...sessions.keys()]) {
@@ -974,15 +975,23 @@ export async function main() {
974
975
  const session = sessions.get(conversationId);
975
976
  if (!session || session.closed)
976
977
  continue;
978
+ if (!session.running && (signal.type !== 'stop_and_drop' || session.queue.length === 0)) {
979
+ await rtdbWrite(`/control/${conversationId}/${agentId}/signal`, null).catch(() => { });
980
+ continue;
981
+ }
977
982
  console.error(`[canon-codex] [${conversationId.slice(0, 8)}] ${signal.type} signal`);
978
- await session.adapter.interrupt();
983
+ if (session.running) {
984
+ await session.adapter.interrupt();
985
+ }
979
986
  session.turnState = 'interrupted';
980
987
  if (signal.type === 'stop_and_drop') {
981
- session.queue.length = 0;
988
+ const droppedPrompts = session.queue.splice(0);
989
+ await markQueuedPromptsRejected(conversationId, droppedPrompts);
982
990
  }
983
991
  writeTurn(session);
984
992
  clearStreaming(conversationId);
985
993
  client.setTyping(conversationId, false).catch(() => { });
994
+ await rtdbWrite(`/control/${conversationId}/${agentId}/signal`, null).catch(() => { });
986
995
  }
987
996
  catch {
988
997
  // Ignore transient RTDB failures.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@canonmsg/codex-plugin",
3
- "version": "0.9.2",
3
+ "version": "0.9.4",
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.10.2",
33
- "@canonmsg/core": "^0.14.0"
32
+ "@canonmsg/agent-sdk": "^1.1.0",
33
+ "@canonmsg/core": "^0.15.0"
34
34
  },
35
35
  "engines": {
36
36
  "node": ">=18.0.0"