@canonmsg/codex-plugin 0.23.2 → 0.23.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.
@@ -1,5 +1,6 @@
1
1
  import { spawn } from 'node:child_process';
2
2
  import { createInterface } from 'node:readline';
3
+ import { normalizePlanStepStatus, renderPlanSteps } from '@canonmsg/coding-agent-host';
3
4
  const DEFAULT_DISCOVERY_REQUEST_TIMEOUT_MS = 5_000;
4
5
  export class CodexAppServerAdapter {
5
6
  cwd;
@@ -783,23 +784,20 @@ function summarizeItem(item) {
783
784
  return 'Searching the web';
784
785
  return null;
785
786
  }
787
+ /**
788
+ * Codex's plan update in the shared plan grammar (`renderPlanSteps` in
789
+ * @canonmsg/coding-agent-host), so a plan card reads the same whichever host
790
+ * produced it. Codex numbers steps by their position in the plan it sent, so an
791
+ * entry with no step text is passed through as a blank that keeps its slot
792
+ * rather than renumbering the steps below it.
793
+ */
786
794
  function renderPlan(params) {
787
795
  const explanation = readString(params, 'explanation');
788
796
  const plan = Array.isArray(params.plan) ? params.plan : [];
789
- const lines = [
790
- explanation ? `**Plan**\n${explanation}` : '**Plan**',
791
- ...plan.flatMap((entry, index) => {
792
- if (!isRecord(entry))
793
- return [];
794
- const step = readString(entry, 'step');
795
- if (!step)
796
- return [];
797
- const status = readString(entry, 'status') ?? 'pending';
798
- const marker = status === 'completed' ? '[x]' : status === 'inProgress' ? '[~]' : '[ ]';
799
- return [`${index + 1}. ${marker} ${step}`];
800
- }),
801
- ];
802
- return lines.join('\n');
797
+ return renderPlanSteps(plan.map((entry) => ({
798
+ text: (isRecord(entry) ? readString(entry, 'step') : undefined) ?? '',
799
+ status: normalizePlanStepStatus(isRecord(entry) ? readString(entry, 'status') : undefined),
800
+ })), { explanation });
803
801
  }
804
802
  function defaultServerRequestResult(method) {
805
803
  if (method.includes('requestApproval'))
package/dist/host.js CHANGED
@@ -5,7 +5,7 @@ import { spawnSync } from 'node:child_process';
5
5
  import { dirname } from 'node:path';
6
6
  import { parseArgs } from 'node:util';
7
7
  import { getCodexImagePath, materializeMessageMedia, materializeReplyContextMedia, sendMediaFileMessage, } from '@canonmsg/agent-sdk';
8
- import { captureTurnArtifactSnapshot, collectTurnArtifacts, } from '@canonmsg/coding-agent-host';
8
+ import { buildTrailBlockId, buildUndeliverableFinalNotice, captureTurnArtifactSnapshot, collectTurnArtifacts, IDLE_TIMEOUT_MS, PLAN_BLOCK_TITLE, } from '@canonmsg/coding-agent-host';
9
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, createTypingStatusPublisher, EXECUTION_ENVIRONMENT_MODES, ExecutionEnvironmentError, CanonClient, CanonStream, DEFAULT_PARTICIPATION_HISTORY_FETCH_LIMIT, DEFAULT_RUNTIME_CAPABILITIES, FINAL_MESSAGE_HANDOFF_MS, getActiveProfileLock, initRTDBAuth, buildLocalRuntimeId, heartbeatLocalRuntimeEntry, markLocalRuntimeStopped, normalizeTurnMetadata, parseRuntimeCardV1, RuntimeRequestManager, prepareConversationEnvironment, loadHostSessionConfig, releaseConversationEnvironment, resolveCanonAgent, verifyResolvedAgentEnvironment, CanonApiError, loadRuntimeSessionState, sendMessageWithRetry, sendMessageWithRetryChunked, saveRuntimeSessionState, buildBoundedTurnTrail, publishHostAgentRuntime, publishHostSessionSnapshots, renderCanonHostInboundContent, renderCodingHostInboundPrompt, resolveHostWorkspaceCwd, shouldTriggerAgentTurn, upsertLocalRuntimeEntry, } from '@canonmsg/core';
10
10
  import { decideAutoReply, } from './inbound-policy.js';
11
11
  import { CodexConversationAdapter, } from './adapter.js';
@@ -74,8 +74,12 @@ export function buildCodexLiveSessionConfig(input) {
74
74
  };
75
75
  }
76
76
  const MAX_SESSIONS = 12;
77
- const IDLE_TIMEOUT_MS = 30 * 60 * 1000;
77
+ // IDLE_TIMEOUT_MS (30 minutes) is shared with the other coding-agent hosts.
78
78
  const HEARTBEAT_MS = 30_000;
79
+ /** How Codex says it finished the work but Canon would not take the answer. */
80
+ const CODEX_UNDELIVERABLE_FINAL_WORDING = {
81
+ lead: 'The Codex host completed the turn, but Canon could not deliver the reply',
82
+ };
79
83
  const IDLE_CHECK_MS = 60_000;
80
84
  const PLAN_REVIEW_TIMEOUT_MS = 10 * 60_000;
81
85
  const CODEX_RUNTIME_CAPABILITIES = {
@@ -1591,10 +1595,14 @@ export async function main() {
1591
1595
  writeTurn(session);
1592
1596
  stopVisibleWorkSignal(session);
1593
1597
  upsertTurnBlock(session, {
1594
- id: `plan:${session.currentTurnId}`,
1598
+ // Spelled through the shared helper, and with the same fallback
1599
+ // the other host uses: a plan arriving before a turn id would
1600
+ // otherwise mint the literal id `plan:null`, which two such turns
1601
+ // would share — one margin row for two plans.
1602
+ id: buildTrailBlockId('plan', session.currentTurnId ?? 'turn'),
1595
1603
  kind: 'plan',
1596
1604
  status: 'running',
1597
- title: 'Plan',
1605
+ title: PLAN_BLOCK_TITLE,
1598
1606
  text: event.text,
1599
1607
  });
1600
1608
  writeCodexStreaming(session, event.text, 'streaming');
@@ -1801,7 +1809,10 @@ export async function main() {
1801
1809
  const message = error instanceof ExecutionEnvironmentError
1802
1810
  ? error.userMessage
1803
1811
  : error instanceof CanonApiError
1804
- ? `The Codex host completed the turn, but Canon could not deliver the reply: ${error.message}`
1812
+ // Shared with the other coding-agent hosts, which is where the bound
1813
+ // on the quoted error comes from: an unbounded API error body would
1814
+ // push this notice over the very limit that rejected the reply.
1815
+ ? buildUndeliverableFinalNotice({ error, wording: CODEX_UNDELIVERABLE_FINAL_WORDING })
1805
1816
  : `The Codex host failed during the turn: ${error instanceof Error ? error.message : String(error)}`;
1806
1817
  session.state.lastError = message;
1807
1818
  writeState(session);
@@ -1,15 +1,12 @@
1
+ import { buildTrailBlockId, normalizeTrailKey } from '@canonmsg/coding-agent-host';
1
2
  export function createCommandBlockTracker() {
2
3
  return {
3
4
  sequence: 0,
4
5
  running: [],
5
6
  };
6
7
  }
7
- function normalizeOptionalString(value) {
8
- const normalized = value?.trim();
9
- return normalized ? normalized : undefined;
10
- }
11
8
  function textBlockId(turnId, itemId) {
12
- return `message:${turnId ?? 'turn'}:${normalizeOptionalString(itemId) ?? 'latest'}`;
9
+ return buildTrailBlockId('message', turnId ?? 'turn', normalizeTrailKey(itemId) ?? 'latest');
13
10
  }
14
11
  function buildLiveText(blocks) {
15
12
  return blocks
@@ -54,12 +51,14 @@ export function applyTextSegmentBlock(state, input) {
54
51
  function nextCommandBlockId(tracker, turnId, itemId) {
55
52
  const stableTurnId = turnId ?? 'turn';
56
53
  if (itemId)
57
- return `command:${stableTurnId}:${itemId}`;
54
+ return buildTrailBlockId('command', stableTurnId, itemId);
55
+ // Older Codex runtimes report no item id, so the per-turn sequence is what
56
+ // keeps two commands from sharing a row.
58
57
  tracker.sequence += 1;
59
- return `command:${stableTurnId}:${tracker.sequence}`;
58
+ return buildTrailBlockId('command', stableTurnId, tracker.sequence);
60
59
  }
61
60
  export function beginCommandBlock(tracker, input) {
62
- const itemId = normalizeOptionalString(input.itemId);
61
+ const itemId = normalizeTrailKey(input.itemId);
63
62
  if (itemId) {
64
63
  const existing = tracker.running.find((block) => block.itemId === itemId);
65
64
  if (existing)
@@ -74,7 +73,7 @@ export function beginCommandBlock(tracker, input) {
74
73
  return blockId;
75
74
  }
76
75
  export function claimCommandBlock(tracker, input) {
77
- const itemId = normalizeOptionalString(input.itemId);
76
+ const itemId = normalizeTrailKey(input.itemId);
78
77
  const command = input.command.trim();
79
78
  const matchingIndex = itemId
80
79
  ? tracker.running.findIndex((block) => block.itemId === itemId)
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@canonmsg/codex-plugin",
3
- "version": "0.23.2",
3
+ "version": "0.23.4",
4
4
  "description": "Canon host integration for Codex CLI",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
@@ -29,9 +29,9 @@
29
29
  "prepack": "npm run build"
30
30
  },
31
31
  "dependencies": {
32
- "@canonmsg/agent-sdk": "^7.0.1",
33
- "@canonmsg/coding-agent-host": "^0.2.2",
34
- "@canonmsg/core": "^7.0.2"
32
+ "@canonmsg/agent-sdk": "^7.1.2",
33
+ "@canonmsg/coding-agent-host": "^0.3.0",
34
+ "@canonmsg/core": "^8.1.0"
35
35
  },
36
36
  "engines": {
37
37
  "node": ">=18.0.0"