@canonmsg/codex-plugin 0.18.4 → 0.18.5
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 +17 -0
- package/dist/host.js +74 -39
- package/package.json +3 -3
package/dist/host.d.ts
CHANGED
|
@@ -1,4 +1,20 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
+
interface HostSessionState {
|
|
3
|
+
lastError?: string;
|
|
4
|
+
model?: string;
|
|
5
|
+
permissionMode?: string;
|
|
6
|
+
effort?: string;
|
|
7
|
+
/** Tokens consumed by the latest completed turn; Codex reports no context window. */
|
|
8
|
+
contextUsage?: {
|
|
9
|
+
totalTokens: number;
|
|
10
|
+
};
|
|
11
|
+
state: 'idle' | 'running';
|
|
12
|
+
}
|
|
13
|
+
export declare function buildCodexInitialSessionState(input: {
|
|
14
|
+
model?: string;
|
|
15
|
+
permissionMode?: string;
|
|
16
|
+
effort?: string | null;
|
|
17
|
+
}): HostSessionState;
|
|
2
18
|
/** GPT reasoning-effort levels, applied next turn via model_reasoning_effort. */
|
|
3
19
|
export declare const CODEX_EFFORT_OPTIONS: readonly [{
|
|
4
20
|
readonly value: "minimal";
|
|
@@ -17,3 +33,4 @@ export declare const CODEX_EFFORT_OPTIONS: readonly [{
|
|
|
17
33
|
readonly label: "Extra high";
|
|
18
34
|
}];
|
|
19
35
|
export declare function main(): Promise<void>;
|
|
36
|
+
export {};
|
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, sendMediaFileMessage, } from '@canonmsg/agent-sdk';
|
|
8
|
-
import {
|
|
9
|
-
import { RUNTIME_NEW_SESSION_ACTION, RUNTIME_STOP_ACTION, RUNTIME_STOP_AND_DROP_ACTION, buildRuntimeCardOutcome, buildPlanApprovalRequest, resolveQuestionAllowOther, 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, CanonApiError, sendMessageWithRetry, sendMessageWithRetryChunked, shouldTriggerAgentTurn, saveRuntimeSessionState, buildBoundedTurnTrail, publishHostAgentRuntime, publishHostSessionSnapshots, renderCanonHostInboundContent,
|
|
8
|
+
import { captureTurnArtifactSnapshot, collectTurnArtifacts, } from '@canonmsg/coding-agent-host';
|
|
9
|
+
import { RUNTIME_NEW_SESSION_ACTION, RUNTIME_STOP_ACTION, RUNTIME_STOP_AND_DROP_ACTION, buildRuntimeCardOutcome, buildPlanApprovalRequest, 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, loadRuntimeSessionState, markLocalRuntimeStopped, normalizeTurnMetadata, parseRuntimeCardV1, prepareConversationEnvironment, loadHostSessionConfig, releaseConversationEnvironment, resolveCanonAgent, CanonApiError, sendMessageWithRetry, sendMessageWithRetryChunked, shouldTriggerAgentTurn, saveRuntimeSessionState, buildBoundedTurnTrail, publishHostAgentRuntime, publishHostSessionSnapshots, renderCanonHostInboundContent, renderCodingHostInboundPrompt, resolveHostWorkspaceCwd, upsertLocalRuntimeEntry, } from '@canonmsg/core';
|
|
10
10
|
import { decideAutoReply, } from './inbound-policy.js';
|
|
11
11
|
import { CodexConversationAdapter, } from './adapter.js';
|
|
12
12
|
import { CodexAppServerAdapter } from './app-server-adapter.js';
|
|
@@ -53,6 +53,14 @@ Keep this terminal open while you want Canon to reach the agent. Closing it,
|
|
|
53
53
|
logging out, rebooting, or sleeping long enough to stop the process takes the
|
|
54
54
|
local agent offline until you revive it. Docs:
|
|
55
55
|
https://canonmail.com/agents/integrations`;
|
|
56
|
+
export function buildCodexInitialSessionState(input) {
|
|
57
|
+
return {
|
|
58
|
+
model: input.model,
|
|
59
|
+
permissionMode: input.permissionMode,
|
|
60
|
+
...(input.effort ? { effort: input.effort } : {}),
|
|
61
|
+
state: 'idle',
|
|
62
|
+
};
|
|
63
|
+
}
|
|
56
64
|
const MAX_SESSIONS = 12;
|
|
57
65
|
const IDLE_TIMEOUT_MS = 30 * 60 * 1000;
|
|
58
66
|
const HEARTBEAT_MS = 30_000;
|
|
@@ -248,7 +256,7 @@ function resolveCodexEffectiveRuntimePolicy(input) {
|
|
|
248
256
|
};
|
|
249
257
|
}
|
|
250
258
|
function buildCanonPrompt(input) {
|
|
251
|
-
return
|
|
259
|
+
return renderCodingHostInboundPrompt(buildCanonInboundFrameV1(buildCanonTurnContextV2({
|
|
252
260
|
content: input.content,
|
|
253
261
|
conversationId: input.conversationId,
|
|
254
262
|
participantContext: input.participantContext,
|
|
@@ -257,9 +265,8 @@ function buildCanonPrompt(input) {
|
|
|
257
265
|
activeSelfContextId: input.activeSelfContextId,
|
|
258
266
|
provenance: input.provenance,
|
|
259
267
|
replyContext: input.replyContext,
|
|
260
|
-
sessionContextLines: input.sessionContextLines,
|
|
261
268
|
message: input.message,
|
|
262
|
-
}));
|
|
269
|
+
})));
|
|
263
270
|
}
|
|
264
271
|
function renderInboundContent(message, materialized) {
|
|
265
272
|
return renderCanonHostInboundContent(message, materialized);
|
|
@@ -867,11 +874,11 @@ export async function main() {
|
|
|
867
874
|
adapter,
|
|
868
875
|
queue: [],
|
|
869
876
|
running: false,
|
|
870
|
-
state: {
|
|
877
|
+
state: buildCodexInitialSessionState({
|
|
871
878
|
model: policy.model,
|
|
872
879
|
permissionMode: policy.permissionMode,
|
|
873
|
-
|
|
874
|
-
},
|
|
880
|
+
effort: initialEffort,
|
|
881
|
+
}),
|
|
875
882
|
policyFingerprint: policy.fingerprint,
|
|
876
883
|
turnState: 'idle',
|
|
877
884
|
currentTurnId: null,
|
|
@@ -907,8 +914,17 @@ export async function main() {
|
|
|
907
914
|
pendingSessionCreations.delete(conversationId);
|
|
908
915
|
}
|
|
909
916
|
}
|
|
910
|
-
function enqueuePrompt(session, prompt, intent = 'queue', toFront = false, sourceMessageId, markAccepted = false, imagePaths = [], mediaAddDirs = [], planMode = false) {
|
|
911
|
-
const nextPrompt = {
|
|
917
|
+
function enqueuePrompt(session, prompt, intent = 'queue', toFront = false, sourceMessageId, markAccepted = false, imagePaths = [], mediaAddDirs = [], planMode = false, artifactRoutingMode = 'disabled') {
|
|
918
|
+
const nextPrompt = {
|
|
919
|
+
prompt,
|
|
920
|
+
intent,
|
|
921
|
+
sourceMessageId,
|
|
922
|
+
markAccepted,
|
|
923
|
+
imagePaths,
|
|
924
|
+
mediaAddDirs,
|
|
925
|
+
planMode,
|
|
926
|
+
artifactRoutingMode,
|
|
927
|
+
};
|
|
912
928
|
if (toFront) {
|
|
913
929
|
session.queue.unshift(nextPrompt);
|
|
914
930
|
}
|
|
@@ -919,6 +935,11 @@ export async function main() {
|
|
|
919
935
|
writeTurn(session);
|
|
920
936
|
void runNextTurn(session);
|
|
921
937
|
}
|
|
938
|
+
function resolveArtifactRoutingMode(participantContext) {
|
|
939
|
+
return participantContext.conversationType === 'direct' && participantContext.isOwner
|
|
940
|
+
? 'workspace-generated'
|
|
941
|
+
: 'disabled';
|
|
942
|
+
}
|
|
922
943
|
async function waitForRuntimeInputResponse(input) {
|
|
923
944
|
while (Date.now() < input.expiresAt) {
|
|
924
945
|
const response = await client.consumeRuntimeInputResponse({
|
|
@@ -1291,43 +1312,50 @@ export async function main() {
|
|
|
1291
1312
|
message: input.message,
|
|
1292
1313
|
});
|
|
1293
1314
|
if (session.running && deliveryIntent === 'interrupt') {
|
|
1294
|
-
|
|
1315
|
+
const artifactRoutingMode = resolveArtifactRoutingMode(participantContext);
|
|
1316
|
+
enqueuePrompt(session, prompt, deliveryIntent, true, input.message.id, shouldMarkAccepted, imagePaths, mediaAddDirs, planCommand.planMode, artifactRoutingMode);
|
|
1295
1317
|
console.error(`[canon-codex] [${input.conversationId.slice(0, 8)}] Interrupting current turn for explicit human send-now`);
|
|
1296
1318
|
await session.adapter.interrupt().catch(() => { });
|
|
1297
1319
|
clearStreaming(input.conversationId);
|
|
1298
1320
|
typingSignals.clear(input.conversationId).catch(() => { });
|
|
1299
1321
|
return;
|
|
1300
1322
|
}
|
|
1301
|
-
|
|
1323
|
+
const artifactRoutingMode = resolveArtifactRoutingMode(participantContext);
|
|
1324
|
+
enqueuePrompt(session, prompt, deliveryIntent, false, input.message.id, shouldMarkAccepted, imagePaths, mediaAddDirs, planCommand.planMode, artifactRoutingMode);
|
|
1302
1325
|
}
|
|
1303
|
-
|
|
1304
|
-
|
|
1326
|
+
function sendTurnArtifactFile(session, file) {
|
|
1327
|
+
return sendMediaFileMessage(client, session.conversationId, file.path, '', {
|
|
1328
|
+
...(session.activeSelfContextId ? { selfContextId: session.activeSelfContextId } : {}),
|
|
1329
|
+
metadata: {
|
|
1330
|
+
...(session.currentTurnId ? { turnId: session.currentTurnId } : {}),
|
|
1331
|
+
// Media is permanent conversation content (promoted like a final
|
|
1332
|
+
// reply) but must never re-trigger other agents — the host's own
|
|
1333
|
+
// final text message stays the only turn-complete trigger.
|
|
1334
|
+
turnSemantics: 'turn_complete',
|
|
1335
|
+
replyBehavior: 'suppress_auto_reply',
|
|
1336
|
+
},
|
|
1337
|
+
});
|
|
1338
|
+
}
|
|
1339
|
+
async function routeWorkspaceGeneratedArtifacts(session, baseline) {
|
|
1340
|
+
if (!baseline)
|
|
1305
1341
|
return;
|
|
1306
1342
|
const logPrefix = `[canon-codex] [${session.conversationId.slice(0, 8)}]`;
|
|
1307
1343
|
try {
|
|
1308
|
-
const result = await
|
|
1309
|
-
|
|
1310
|
-
|
|
1311
|
-
...(session.activeSelfContextId ? { selfContextId: session.activeSelfContextId } : {}),
|
|
1312
|
-
metadata: {
|
|
1313
|
-
...(session.currentTurnId ? { turnId: session.currentTurnId } : {}),
|
|
1314
|
-
// Media is permanent conversation content (promoted like a final
|
|
1315
|
-
// reply) but must never re-trigger other agents — the host's own
|
|
1316
|
-
// final text message stays the only turn-complete trigger.
|
|
1317
|
-
turnSemantics: 'turn_complete',
|
|
1318
|
-
replyBehavior: 'suppress_auto_reply',
|
|
1319
|
-
},
|
|
1320
|
-
}),
|
|
1344
|
+
const result = await collectTurnArtifacts({
|
|
1345
|
+
cwd: session.cwd,
|
|
1346
|
+
baseline,
|
|
1321
1347
|
});
|
|
1322
|
-
for (const
|
|
1323
|
-
|
|
1324
|
-
|
|
1325
|
-
|
|
1326
|
-
|
|
1327
|
-
|
|
1348
|
+
for (const file of result.files) {
|
|
1349
|
+
try {
|
|
1350
|
+
const { messageId } = await sendTurnArtifactFile(session, file);
|
|
1351
|
+
console.error(`${logPrefix} Routed generated artifact ${file.relativePath ?? file.fileName} (${messageId})`);
|
|
1352
|
+
}
|
|
1353
|
+
catch (error) {
|
|
1354
|
+
console.error(`${logPrefix} Artifact upload failed for ${file.relativePath ?? file.fileName}: ${error instanceof Error ? error.message : String(error)}`);
|
|
1355
|
+
}
|
|
1328
1356
|
}
|
|
1329
1357
|
for (const skipped of result.skipped) {
|
|
1330
|
-
if (skipped.reason === 'too-large' || skipped.reason === 'file-cap' || skipped.reason === '
|
|
1358
|
+
if (skipped.reason === 'too-large' || skipped.reason === 'file-cap' || skipped.reason === 'scan-limit') {
|
|
1331
1359
|
console.error(`${logPrefix} Artifact skipped ${skipped.fileName} (${skipped.reason})`);
|
|
1332
1360
|
}
|
|
1333
1361
|
}
|
|
@@ -1361,19 +1389,26 @@ export async function main() {
|
|
|
1361
1389
|
// Status-only seed: 'thinking' renders as a working filament row on the
|
|
1362
1390
|
// clients; text here would be bubbled as speech (v4 register rule).
|
|
1363
1391
|
writeCodexStreaming(session, '', 'thinking');
|
|
1364
|
-
let
|
|
1392
|
+
let artifactBaseline = null;
|
|
1365
1393
|
let artifactsRouted = false;
|
|
1366
1394
|
const routeArtifactsOnce = async () => {
|
|
1367
1395
|
if (artifactsRouted)
|
|
1368
1396
|
return;
|
|
1369
1397
|
artifactsRouted = true;
|
|
1370
|
-
|
|
1398
|
+
if (nextTurn.artifactRoutingMode === 'workspace-generated') {
|
|
1399
|
+
await routeWorkspaceGeneratedArtifacts(session, artifactBaseline);
|
|
1400
|
+
}
|
|
1371
1401
|
};
|
|
1372
1402
|
try {
|
|
1373
1403
|
const turnId = session.currentTurnId ?? randomUUID();
|
|
1374
1404
|
session.currentTurnId = turnId;
|
|
1375
|
-
|
|
1376
|
-
|
|
1405
|
+
let turnPrompt = nextTurn.prompt;
|
|
1406
|
+
if (nextTurn.artifactRoutingMode === 'workspace-generated') {
|
|
1407
|
+
artifactBaseline = await captureTurnArtifactSnapshot({ cwd: session.cwd }).catch((error) => {
|
|
1408
|
+
console.error(`[canon-codex] [${session.conversationId.slice(0, 8)}] Artifact snapshot failed:`, error instanceof Error ? error.message : error);
|
|
1409
|
+
return null;
|
|
1410
|
+
});
|
|
1411
|
+
}
|
|
1377
1412
|
const modelGuard = buildCodexModelGuardMessage(session.state.model, codexCliStatus);
|
|
1378
1413
|
if (modelGuard) {
|
|
1379
1414
|
throw new ExecutionEnvironmentError(modelGuard, modelGuard);
|
|
@@ -1824,7 +1859,7 @@ export async function main() {
|
|
|
1824
1859
|
{
|
|
1825
1860
|
id: 'mediaOut',
|
|
1826
1861
|
label: 'Media out',
|
|
1827
|
-
value: '
|
|
1862
|
+
value: 'Generated media artifacts',
|
|
1828
1863
|
},
|
|
1829
1864
|
],
|
|
1830
1865
|
execution: {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@canonmsg/codex-plugin",
|
|
3
|
-
"version": "0.18.
|
|
3
|
+
"version": "0.18.5",
|
|
4
4
|
"description": "Canon host integration for Codex CLI",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/index.js",
|
|
@@ -30,8 +30,8 @@
|
|
|
30
30
|
},
|
|
31
31
|
"dependencies": {
|
|
32
32
|
"@canonmsg/agent-sdk": "^3.2.3",
|
|
33
|
-
"@canonmsg/coding-agent-host": "^0.2.
|
|
34
|
-
"@canonmsg/core": "^2.
|
|
33
|
+
"@canonmsg/coding-agent-host": "^0.2.2",
|
|
34
|
+
"@canonmsg/core": "^2.8.0"
|
|
35
35
|
},
|
|
36
36
|
"engines": {
|
|
37
37
|
"node": ">=18.0.0"
|