@canonmsg/codex-plugin 0.14.0 → 0.14.1

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 +43 -16
  2. package/package.json +3 -3
package/dist/host.js CHANGED
@@ -74,19 +74,6 @@ let workspaceRoots = [];
74
74
  let workspaceRootMetadata = [];
75
75
  function buildCodexRuntimeDescriptor(input) {
76
76
  const commands = [
77
- ...(input.supportsPlanMode
78
- ? [{
79
- id: 'plan',
80
- label: 'Plan first',
81
- description: 'Ask Codex to plan before implementing. Text after /plan becomes the planning prompt.',
82
- aliases: ['plan'],
83
- category: 'plan',
84
- placements: ['composer_slash', 'command_palette'],
85
- availability: ['always'],
86
- trailingTextBehavior: 'send_as_prompt',
87
- dispatch: { kind: 'text_passthrough', template: '/plan {argument}' },
88
- }]
89
- : []),
90
77
  {
91
78
  id: 'runtime-status',
92
79
  label: 'Runtime status',
@@ -115,6 +102,29 @@ function buildCodexRuntimeDescriptor(input) {
115
102
  defaultPermissionMode: input.defaultPermissionMode,
116
103
  presentation: input.presentation,
117
104
  streamingTextMode: 'snapshot',
105
+ ...(input.supportsPlanMode
106
+ ? {
107
+ turnModes: [
108
+ {
109
+ id: 'normal',
110
+ label: 'Normal',
111
+ description: 'Let Codex answer or act normally.',
112
+ scope: 'next_turn',
113
+ default: true,
114
+ activation: { kind: 'message_metadata', value: 'normal' },
115
+ },
116
+ {
117
+ id: 'plan',
118
+ label: 'Plan',
119
+ description: 'Ask Codex to plan before implementing.',
120
+ scope: 'next_turn',
121
+ ownerOnly: true,
122
+ aliases: ['plan'],
123
+ activation: { kind: 'message_metadata', value: 'plan' },
124
+ },
125
+ ],
126
+ }
127
+ : {}),
118
128
  commands,
119
129
  ...(input.supportsRichCards
120
130
  ? {
@@ -1148,6 +1158,10 @@ export async function main() {
1148
1158
  }
1149
1159
  async function enqueueInboundMessage(input) {
1150
1160
  knownConversationIds.add(input.conversationId);
1161
+ if (input.turnDispatch && input.turnDispatch.kind !== 'run_turn') {
1162
+ console.error(`[canon-codex] [${input.conversationId.slice(0, 8)}] Suppressed server-dispatched turn: ${input.turnDispatch.reason}`);
1163
+ return;
1164
+ }
1151
1165
  if (isRecord(input.message.metadata)
1152
1166
  && input.message.metadata.type === 'plan_approval_reply'
1153
1167
  && typeof input.message.metadata.decision === 'string') {
@@ -1173,8 +1187,12 @@ export async function main() {
1173
1187
  }
1174
1188
  }
1175
1189
  const renderedContent = renderInboundContent(input.message, materialized);
1190
+ const turnMetadata = normalizeTurnMetadata(input.message.metadata);
1191
+ const requestedPlanMode = turnMetadata?.requestedTurnMode === 'plan';
1176
1192
  const planCommand = useAppServer
1177
- ? parsePlanCommand(renderedContent)
1193
+ ? requestedPlanMode
1194
+ ? { planMode: true, content: renderedContent }
1195
+ : parsePlanCommand(renderedContent)
1178
1196
  : { planMode: false, content: renderedContent };
1179
1197
  const content = planCommand.content;
1180
1198
  const hydrated = await loadHydratedInboundContext({
@@ -1203,14 +1221,18 @@ export async function main() {
1203
1221
  .filter((path) => path !== null);
1204
1222
  const mediaAddDirs = uniqueStrings(promptMaterialized.map((attachment) => dirname(attachment.path)));
1205
1223
  const participantContext = hydrated.participantContext;
1206
- const autoReply = decideAutoReply(participantContext, behavior);
1224
+ const autoReply = input.turnDispatch?.kind === 'run_turn'
1225
+ ? {
1226
+ allow: true,
1227
+ reason: input.turnDispatch.reason || 'server dispatch allowed this turn',
1228
+ }
1229
+ : decideAutoReply(participantContext, behavior);
1207
1230
  if (!autoReply.allow) {
1208
1231
  console.error(`[canon-codex] [${input.conversationId.slice(0, 8)}] Suppressed auto-reply: ${autoReply.reason}`);
1209
1232
  return;
1210
1233
  }
1211
1234
  console.error(`[canon-codex] [${input.conversationId.slice(0, 8)}] Message from ${input.senderName}: "${content.slice(0, 80)}" (${autoReply.reason})`);
1212
1235
  markGroupContextModeUsed(input.conversationId, participantContext.groupContextMode);
1213
- const turnMetadata = normalizeTurnMetadata(input.message.metadata);
1214
1236
  const deliveryIntent = turnMetadata?.deliveryIntent ?? 'queue';
1215
1237
  const shouldMarkAccepted = turnMetadata?.inboundDisposition === 'queued';
1216
1238
  let session;
@@ -1677,6 +1699,10 @@ export async function main() {
1677
1699
  const message = payload.message;
1678
1700
  if (message.senderId === agentId)
1679
1701
  return;
1702
+ if (payload.turnDispatch && payload.turnDispatch.kind !== 'run_turn') {
1703
+ console.error(`[canon-codex] [${payload.conversationId.slice(0, 8)}] Ignoring server-dispatched observe-only message: ${payload.turnDispatch.reason}`);
1704
+ return;
1705
+ }
1680
1706
  void enqueueInboundMessage({
1681
1707
  conversationId: payload.conversationId,
1682
1708
  message,
@@ -1686,6 +1712,7 @@ export async function main() {
1686
1712
  activeSelfContextId: payload.activeSelfContextId,
1687
1713
  selfContexts: payload.selfContexts,
1688
1714
  provenance: payload.provenance,
1715
+ turnDispatch: payload.turnDispatch,
1689
1716
  });
1690
1717
  if (message.id) {
1691
1718
  saveRuntimeSessionState(runtimeId, {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@canonmsg/codex-plugin",
3
- "version": "0.14.0",
3
+ "version": "0.14.1",
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": "^3.0.0",
33
- "@canonmsg/core": "^2.0.0"
32
+ "@canonmsg/agent-sdk": "^3.1.0",
33
+ "@canonmsg/core": "^2.1.0"
34
34
  },
35
35
  "engines": {
36
36
  "node": ">=18.0.0"