@canonmsg/codex-plugin 0.13.3 → 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.
- package/dist/host.js +56 -39
- package/package.json +4 -4
package/dist/host.js
CHANGED
|
@@ -5,8 +5,8 @@ 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, } from '@canonmsg/agent-sdk';
|
|
8
|
-
import { RUNTIME_NEW_SESSION_ACTION, RUNTIME_STOP_ACTION, RUNTIME_STOP_AND_DROP_ACTION, buildRuntimeCardOutcome, buildPlanApprovalRequest,
|
|
9
|
-
import {
|
|
8
|
+
import { RUNTIME_NEW_SESSION_ACTION, RUNTIME_STOP_ACTION, RUNTIME_STOP_AND_DROP_ACTION, buildRuntimeCardOutcome, buildPlanApprovalRequest, buildCanonTurnContextV2, buildConfiguredWorkspaceOptionsWithRoots, buildFirstPartyCodingRuntimeDescriptor, buildHydratedInboundContext, diffCanonMemberIds, buildPublicWorkspaceRoots, buildPublicWorkspaceOptions, buildRuntimePresentationPolicy, 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, loadRuntimeSessionState, markLocalRuntimeStopped, normalizeTurnMetadata, parseRuntimeCardV1, prepareConversationEnvironment, loadHostSessionConfig, releaseConversationEnvironment, resolveCanonAgent, rtdbRead, rtdbWrite, CanonApiError, sendMessageWithRetry, sendMessageWithRetryChunked, shouldTriggerAgentTurn, saveRuntimeSessionState, buildBoundedTurnTrail, publishHostAgentRuntime, publishHostSessionSnapshots, renderCanonHostInboundContent, renderCanonTurnBriefPrompt, resolveHostWorkspaceCwd, upsertLocalRuntimeEntry, } from '@canonmsg/core';
|
|
9
|
+
import { decideAutoReply, } from './inbound-policy.js';
|
|
10
10
|
import { CodexConversationAdapter, } from './adapter.js';
|
|
11
11
|
import { CodexAppServerAdapter } from './app-server-adapter.js';
|
|
12
12
|
import { mapCanonApprovalResultToCodexDecision, mapCodexAppServerApprovalRequest, } from './app-server-approval.js';
|
|
@@ -50,7 +50,6 @@ Keep this terminal open while you want Canon to reach the agent. Closing it,
|
|
|
50
50
|
logging out, rebooting, or sleeping long enough to stop the process takes the
|
|
51
51
|
local agent offline until you revive it. Docs:
|
|
52
52
|
https://canonmail.com/agents/integrations`;
|
|
53
|
-
const HOST_PROMPT_MODE = resolveCanonHostPromptMode(process.env.CANON_HOST_PROMPT_MODE);
|
|
54
53
|
const MAX_SESSIONS = 12;
|
|
55
54
|
const IDLE_TIMEOUT_MS = 30 * 60 * 1000;
|
|
56
55
|
const HEARTBEAT_MS = 30_000;
|
|
@@ -75,19 +74,6 @@ let workspaceRoots = [];
|
|
|
75
74
|
let workspaceRootMetadata = [];
|
|
76
75
|
function buildCodexRuntimeDescriptor(input) {
|
|
77
76
|
const commands = [
|
|
78
|
-
...(input.supportsPlanMode
|
|
79
|
-
? [{
|
|
80
|
-
id: 'plan',
|
|
81
|
-
label: 'Plan first',
|
|
82
|
-
description: 'Ask Codex to plan before implementing. Text after /plan becomes the planning prompt.',
|
|
83
|
-
aliases: ['plan'],
|
|
84
|
-
category: 'plan',
|
|
85
|
-
placements: ['composer_slash', 'command_palette'],
|
|
86
|
-
availability: ['always'],
|
|
87
|
-
trailingTextBehavior: 'send_as_prompt',
|
|
88
|
-
dispatch: { kind: 'text_passthrough', template: '/plan {argument}' },
|
|
89
|
-
}]
|
|
90
|
-
: []),
|
|
91
77
|
{
|
|
92
78
|
id: 'runtime-status',
|
|
93
79
|
label: 'Runtime status',
|
|
@@ -116,6 +102,29 @@ function buildCodexRuntimeDescriptor(input) {
|
|
|
116
102
|
defaultPermissionMode: input.defaultPermissionMode,
|
|
117
103
|
presentation: input.presentation,
|
|
118
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
|
+
: {}),
|
|
119
128
|
commands,
|
|
120
129
|
...(input.supportsRichCards
|
|
121
130
|
? {
|
|
@@ -229,25 +238,17 @@ function resolveCodexEffectiveRuntimePolicy(input) {
|
|
|
229
238
|
};
|
|
230
239
|
}
|
|
231
240
|
function buildCanonPrompt(input) {
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
hydratedFromPage: input.hydratedFromPage,
|
|
244
|
-
}));
|
|
245
|
-
}
|
|
246
|
-
return buildCanonHostPrompt({
|
|
247
|
-
hostLabel: 'Codex',
|
|
248
|
-
buildInboundContextLines,
|
|
249
|
-
...input,
|
|
250
|
-
});
|
|
241
|
+
return renderCanonTurnBriefPrompt(buildCanonTurnContextV2({
|
|
242
|
+
content: input.content,
|
|
243
|
+
conversationId: input.conversationId,
|
|
244
|
+
participantContext: input.participantContext,
|
|
245
|
+
behavior: input.behavior,
|
|
246
|
+
selfContexts: input.selfContexts,
|
|
247
|
+
activeSelfContextId: input.activeSelfContextId,
|
|
248
|
+
provenance: input.provenance,
|
|
249
|
+
replyContext: input.replyContext,
|
|
250
|
+
message: input.message,
|
|
251
|
+
}));
|
|
251
252
|
}
|
|
252
253
|
function renderInboundContent(message, materialized) {
|
|
253
254
|
return renderCanonHostInboundContent(message, materialized);
|
|
@@ -1157,6 +1158,10 @@ export async function main() {
|
|
|
1157
1158
|
}
|
|
1158
1159
|
async function enqueueInboundMessage(input) {
|
|
1159
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
|
+
}
|
|
1160
1165
|
if (isRecord(input.message.metadata)
|
|
1161
1166
|
&& input.message.metadata.type === 'plan_approval_reply'
|
|
1162
1167
|
&& typeof input.message.metadata.decision === 'string') {
|
|
@@ -1182,8 +1187,12 @@ export async function main() {
|
|
|
1182
1187
|
}
|
|
1183
1188
|
}
|
|
1184
1189
|
const renderedContent = renderInboundContent(input.message, materialized);
|
|
1190
|
+
const turnMetadata = normalizeTurnMetadata(input.message.metadata);
|
|
1191
|
+
const requestedPlanMode = turnMetadata?.requestedTurnMode === 'plan';
|
|
1185
1192
|
const planCommand = useAppServer
|
|
1186
|
-
?
|
|
1193
|
+
? requestedPlanMode
|
|
1194
|
+
? { planMode: true, content: renderedContent }
|
|
1195
|
+
: parsePlanCommand(renderedContent)
|
|
1187
1196
|
: { planMode: false, content: renderedContent };
|
|
1188
1197
|
const content = planCommand.content;
|
|
1189
1198
|
const hydrated = await loadHydratedInboundContext({
|
|
@@ -1212,14 +1221,18 @@ export async function main() {
|
|
|
1212
1221
|
.filter((path) => path !== null);
|
|
1213
1222
|
const mediaAddDirs = uniqueStrings(promptMaterialized.map((attachment) => dirname(attachment.path)));
|
|
1214
1223
|
const participantContext = hydrated.participantContext;
|
|
1215
|
-
const autoReply =
|
|
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);
|
|
1216
1230
|
if (!autoReply.allow) {
|
|
1217
1231
|
console.error(`[canon-codex] [${input.conversationId.slice(0, 8)}] Suppressed auto-reply: ${autoReply.reason}`);
|
|
1218
1232
|
return;
|
|
1219
1233
|
}
|
|
1220
1234
|
console.error(`[canon-codex] [${input.conversationId.slice(0, 8)}] Message from ${input.senderName}: "${content.slice(0, 80)}" (${autoReply.reason})`);
|
|
1221
1235
|
markGroupContextModeUsed(input.conversationId, participantContext.groupContextMode);
|
|
1222
|
-
const turnMetadata = normalizeTurnMetadata(input.message.metadata);
|
|
1223
1236
|
const deliveryIntent = turnMetadata?.deliveryIntent ?? 'queue';
|
|
1224
1237
|
const shouldMarkAccepted = turnMetadata?.inboundDisposition === 'queued';
|
|
1225
1238
|
let session;
|
|
@@ -1252,7 +1265,6 @@ export async function main() {
|
|
|
1252
1265
|
provenance: hydrated.provenance,
|
|
1253
1266
|
replyContext,
|
|
1254
1267
|
message: input.message,
|
|
1255
|
-
hydratedFromPage: hydrated.hydratedFromPage,
|
|
1256
1268
|
});
|
|
1257
1269
|
if (session.running && deliveryIntent === 'interrupt') {
|
|
1258
1270
|
enqueuePrompt(session, prompt, deliveryIntent, true, input.message.id, shouldMarkAccepted, imagePaths, mediaAddDirs, planCommand.planMode);
|
|
@@ -1687,6 +1699,10 @@ export async function main() {
|
|
|
1687
1699
|
const message = payload.message;
|
|
1688
1700
|
if (message.senderId === agentId)
|
|
1689
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
|
+
}
|
|
1690
1706
|
void enqueueInboundMessage({
|
|
1691
1707
|
conversationId: payload.conversationId,
|
|
1692
1708
|
message,
|
|
@@ -1696,6 +1712,7 @@ export async function main() {
|
|
|
1696
1712
|
activeSelfContextId: payload.activeSelfContextId,
|
|
1697
1713
|
selfContexts: payload.selfContexts,
|
|
1698
1714
|
provenance: payload.provenance,
|
|
1715
|
+
turnDispatch: payload.turnDispatch,
|
|
1699
1716
|
});
|
|
1700
1717
|
if (message.id) {
|
|
1701
1718
|
saveRuntimeSessionState(runtimeId, {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@canonmsg/codex-plugin",
|
|
3
|
-
"version": "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": "^
|
|
33
|
-
"@canonmsg/core": "^1.
|
|
32
|
+
"@canonmsg/agent-sdk": "^3.1.0",
|
|
33
|
+
"@canonmsg/core": "^2.1.0"
|
|
34
34
|
},
|
|
35
35
|
"engines": {
|
|
36
36
|
"node": ">=18.0.0"
|
|
@@ -54,7 +54,7 @@
|
|
|
54
54
|
"devDependencies": {
|
|
55
55
|
"@types/node": "^22.0.0",
|
|
56
56
|
"typescript": "~5.7.0",
|
|
57
|
-
"vitest": "^
|
|
57
|
+
"vitest": "^4.1.8"
|
|
58
58
|
},
|
|
59
59
|
"license": "MIT"
|
|
60
60
|
}
|