@canonmsg/codex-plugin 0.13.0 → 0.13.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 +44 -23
- package/package.json +3 -3
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, } from '@canonmsg/agent-sdk';
|
|
8
|
-
import { RUNTIME_NEW_SESSION_ACTION, RUNTIME_STOP_ACTION, RUNTIME_STOP_AND_DROP_ACTION, buildRuntimeCardOutcome, buildPlanApprovalRequest, buildCanonHostPrompt, buildConfiguredWorkspaceOptionsWithRoots, buildFirstPartyCodingRuntimeDescriptor, buildHydratedInboundContext, diffCanonMemberIds, buildPublicWorkspaceRoots, buildPublicWorkspaceOptions, buildRuntimePresentationPolicy, DEFAULT_FIRST_PARTY_RUNTIME_PRESENTATION, 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, parseRuntimeCardV1, prepareConversationEnvironment, loadHostSessionConfig, releaseConversationEnvironment, resolveCanonAgent, rtdbRead, rtdbWrite, shouldTriggerAgentTurn, saveRuntimeSessionState, buildBoundedTurnTrail, publishHostAgentRuntime, publishHostSessionSnapshots, renderCanonHostInboundContent, resolveHostWorkspaceCwd, upsertLocalRuntimeEntry, } from '@canonmsg/core';
|
|
8
|
+
import { RUNTIME_NEW_SESSION_ACTION, RUNTIME_STOP_ACTION, RUNTIME_STOP_AND_DROP_ACTION, buildRuntimeCardOutcome, buildPlanApprovalRequest, buildCanonHostPrompt, 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, sendMessageWithRetry, shouldTriggerAgentTurn, saveRuntimeSessionState, buildBoundedTurnTrail, publishHostAgentRuntime, publishHostSessionSnapshots, renderCanonHostInboundContent, resolveHostWorkspaceCwd, upsertLocalRuntimeEntry, } from '@canonmsg/core';
|
|
9
9
|
import { buildInboundContextLines, decideAutoReply, } from './inbound-policy.js';
|
|
10
10
|
import { CodexConversationAdapter, } from './adapter.js';
|
|
11
11
|
import { CodexAppServerAdapter } from './app-server-adapter.js';
|
|
@@ -54,6 +54,8 @@ const IDLE_TIMEOUT_MS = 30 * 60 * 1000;
|
|
|
54
54
|
const HEARTBEAT_MS = 30_000;
|
|
55
55
|
const IDLE_CHECK_MS = 60_000;
|
|
56
56
|
const CONTROL_POLL_MS = 2_000;
|
|
57
|
+
const IDLE_CONTROL_POLL_MS = 10_000;
|
|
58
|
+
const CONTROL_POLL_JITTER_MS = 1_000;
|
|
57
59
|
const CODEX_RUNTIME_CAPABILITIES = {
|
|
58
60
|
...DEFAULT_RUNTIME_CAPABILITIES,
|
|
59
61
|
supportsInterrupt: true,
|
|
@@ -61,6 +63,10 @@ const CODEX_RUNTIME_CAPABILITIES = {
|
|
|
61
63
|
supportsQueue: true,
|
|
62
64
|
supportsNonFinalPermanentMessages: false,
|
|
63
65
|
};
|
|
66
|
+
function controlPollDelayMs(hasActiveWork) {
|
|
67
|
+
const base = hasActiveWork ? CONTROL_POLL_MS : IDLE_CONTROL_POLL_MS;
|
|
68
|
+
return base + Math.floor(Math.random() * CONTROL_POLL_JITTER_MS);
|
|
69
|
+
}
|
|
64
70
|
let workingDir = process.cwd();
|
|
65
71
|
let workspaceOptions = [];
|
|
66
72
|
let workspaceRoots = [];
|
|
@@ -390,6 +396,11 @@ export async function main() {
|
|
|
390
396
|
console.error(`[canon-codex] Starting${profile ? ` (profile: ${profile})` : ''} in ${workingDir}`);
|
|
391
397
|
const client = new CanonClient(apiKey, baseUrl);
|
|
392
398
|
initRTDBAuth(client);
|
|
399
|
+
const typingSignals = createTypingStatusPublisher({
|
|
400
|
+
setTyping: (conversationId, typing, status) => status
|
|
401
|
+
? client.setTyping(conversationId, typing, status)
|
|
402
|
+
: client.setTyping(conversationId, typing),
|
|
403
|
+
});
|
|
393
404
|
let agentId;
|
|
394
405
|
let ownerId = null;
|
|
395
406
|
let ownerName = null;
|
|
@@ -660,31 +671,33 @@ export async function main() {
|
|
|
660
671
|
turnId: session.currentTurnId ?? block.turnId,
|
|
661
672
|
})));
|
|
662
673
|
}
|
|
674
|
+
function buildCodexMessageId(session, kind) {
|
|
675
|
+
return `codex-${kind}-${session.currentTurnId ?? randomUUID()}`;
|
|
676
|
+
}
|
|
677
|
+
function buildCodexRuntimeCardOutcomeMessageId(cardId, status) {
|
|
678
|
+
return `codex-card-${cardId}-${status}`;
|
|
679
|
+
}
|
|
663
680
|
async function handoffFinalMessage(conversationId) {
|
|
664
681
|
await sleep(FINAL_MESSAGE_HANDOFF_MS);
|
|
665
682
|
clearStreaming(conversationId);
|
|
666
|
-
|
|
683
|
+
typingSignals.clear(conversationId).catch(() => { });
|
|
667
684
|
}
|
|
668
685
|
function refreshVisibleWorkSignal(session) {
|
|
669
686
|
if (!session.running || session.closed)
|
|
670
687
|
return;
|
|
671
688
|
if (session.turnState !== 'thinking' && session.turnState !== 'tool')
|
|
672
689
|
return;
|
|
673
|
-
|
|
690
|
+
typingSignals.start(session.conversationId, 'thinking').catch(() => { });
|
|
674
691
|
}
|
|
675
692
|
function startVisibleWorkSignal(session) {
|
|
676
693
|
refreshVisibleWorkSignal(session);
|
|
677
|
-
if (session.typingKeepaliveTimer)
|
|
678
|
-
return;
|
|
679
|
-
session.typingKeepaliveTimer = setInterval(() => {
|
|
680
|
-
refreshVisibleWorkSignal(session);
|
|
681
|
-
}, 3500);
|
|
682
694
|
}
|
|
683
695
|
function stopVisibleWorkSignal(session) {
|
|
684
696
|
if (session.typingKeepaliveTimer) {
|
|
685
697
|
clearInterval(session.typingKeepaliveTimer);
|
|
686
698
|
session.typingKeepaliveTimer = null;
|
|
687
699
|
}
|
|
700
|
+
typingSignals.clear(session.conversationId).catch(() => { });
|
|
688
701
|
}
|
|
689
702
|
function closeSession(conversationId) {
|
|
690
703
|
const session = sessions.get(conversationId);
|
|
@@ -699,7 +712,8 @@ export async function main() {
|
|
|
699
712
|
clearStreaming(conversationId);
|
|
700
713
|
runtimeState.clearSessionState(conversationId).catch(() => { });
|
|
701
714
|
runtimeState.clearTurnState(conversationId).catch(() => { });
|
|
702
|
-
|
|
715
|
+
typingSignals.clear(conversationId).catch(() => { });
|
|
716
|
+
typingSignals.dispose(conversationId);
|
|
703
717
|
sessions.delete(conversationId);
|
|
704
718
|
}
|
|
705
719
|
async function resetRuntimeSession(session) {
|
|
@@ -725,7 +739,7 @@ export async function main() {
|
|
|
725
739
|
}
|
|
726
740
|
stopVisibleWorkSignal(session);
|
|
727
741
|
clearStreaming(conversationId);
|
|
728
|
-
|
|
742
|
+
typingSignals.clear(conversationId).catch(() => { });
|
|
729
743
|
writeState(session);
|
|
730
744
|
writeTurn(session);
|
|
731
745
|
}
|
|
@@ -1005,7 +1019,8 @@ export async function main() {
|
|
|
1005
1019
|
});
|
|
1006
1020
|
requestResolved = true;
|
|
1007
1021
|
const outcome = buildRuntimeCardOutcome(cardId, response.status, { reason: response.status });
|
|
1008
|
-
await client
|
|
1022
|
+
await sendMessageWithRetry(client, session.conversationId, outcome.text, {
|
|
1023
|
+
messageId: buildCodexRuntimeCardOutcomeMessageId(cardId, response.status),
|
|
1009
1024
|
metadata: {
|
|
1010
1025
|
...outcome.metadata,
|
|
1011
1026
|
turnId: session.currentTurnId ?? undefined,
|
|
@@ -1031,7 +1046,8 @@ export async function main() {
|
|
|
1031
1046
|
cancel: true,
|
|
1032
1047
|
}).catch(() => null);
|
|
1033
1048
|
const outcome = buildRuntimeCardOutcome(cardId, 'cancelled', { reason: 'interrupted' });
|
|
1034
|
-
await client
|
|
1049
|
+
await sendMessageWithRetry(client, session.conversationId, outcome.text, {
|
|
1050
|
+
messageId: buildCodexRuntimeCardOutcomeMessageId(cardId, 'cancelled'),
|
|
1035
1051
|
metadata: {
|
|
1036
1052
|
...outcome.metadata,
|
|
1037
1053
|
turnId: session.currentTurnId ?? undefined,
|
|
@@ -1197,7 +1213,8 @@ export async function main() {
|
|
|
1197
1213
|
const userMessage = error instanceof ExecutionEnvironmentError ? error.userMessage : message;
|
|
1198
1214
|
console.error(`[canon-codex] [${input.conversationId.slice(0, 8)}] Failed to create session: ${message}`);
|
|
1199
1215
|
await markQueuedMessageAccepted(input.conversationId, input.message.id, shouldMarkAccepted);
|
|
1200
|
-
await client
|
|
1216
|
+
await sendMessageWithRetry(client, input.conversationId, `I couldn't start a coding session for this workspace: ${userMessage}`, {
|
|
1217
|
+
messageId: `codex-start-failed-${input.message.id}`,
|
|
1201
1218
|
...(activeSelfContextId ? { selfContextId: activeSelfContextId } : {}),
|
|
1202
1219
|
metadata: {
|
|
1203
1220
|
turnSemantics: 'turn_complete',
|
|
@@ -1220,7 +1237,7 @@ export async function main() {
|
|
|
1220
1237
|
console.error(`[canon-codex] [${input.conversationId.slice(0, 8)}] Interrupting current turn for explicit human send-now`);
|
|
1221
1238
|
await session.adapter.interrupt().catch(() => { });
|
|
1222
1239
|
clearStreaming(input.conversationId);
|
|
1223
|
-
|
|
1240
|
+
typingSignals.clear(input.conversationId).catch(() => { });
|
|
1224
1241
|
return;
|
|
1225
1242
|
}
|
|
1226
1243
|
enqueuePrompt(session, prompt, deliveryIntent, false, input.message.id, shouldMarkAccepted, imagePaths, mediaAddDirs, planCommand.planMode);
|
|
@@ -1269,7 +1286,6 @@ export async function main() {
|
|
|
1269
1286
|
markTurnProgress(session);
|
|
1270
1287
|
writeTurn(session);
|
|
1271
1288
|
stopVisibleWorkSignal(session);
|
|
1272
|
-
client.setTyping(session.conversationId, false).catch(() => { });
|
|
1273
1289
|
writeCodexStreaming(session, event.text, 'streaming');
|
|
1274
1290
|
return;
|
|
1275
1291
|
}
|
|
@@ -1278,7 +1294,6 @@ export async function main() {
|
|
|
1278
1294
|
markTurnProgress(session);
|
|
1279
1295
|
writeTurn(session);
|
|
1280
1296
|
stopVisibleWorkSignal(session);
|
|
1281
|
-
client.setTyping(session.conversationId, false).catch(() => { });
|
|
1282
1297
|
upsertTurnBlock(session, {
|
|
1283
1298
|
id: `plan:${session.currentTurnId}`,
|
|
1284
1299
|
kind: 'plan',
|
|
@@ -1357,7 +1372,8 @@ export async function main() {
|
|
|
1357
1372
|
title: 'Codex Plan',
|
|
1358
1373
|
body: result.finalMessage,
|
|
1359
1374
|
});
|
|
1360
|
-
await client
|
|
1375
|
+
await sendMessageWithRetry(client, session.conversationId, planApproval.text, {
|
|
1376
|
+
messageId: buildCodexMessageId(session, 'plan'),
|
|
1361
1377
|
metadata: {
|
|
1362
1378
|
...planApproval.metadata,
|
|
1363
1379
|
turnId: session.currentTurnId,
|
|
@@ -1373,7 +1389,8 @@ export async function main() {
|
|
|
1373
1389
|
clearStoredThread();
|
|
1374
1390
|
}
|
|
1375
1391
|
const turnTrail = buildFinalTurnTrail(session);
|
|
1376
|
-
await client
|
|
1392
|
+
await sendMessageWithRetry(client, session.conversationId, result.finalMessage, {
|
|
1393
|
+
messageId: buildCodexMessageId(session, 'final'),
|
|
1377
1394
|
...(session.activeSelfContextId
|
|
1378
1395
|
? { selfContextId: session.activeSelfContextId }
|
|
1379
1396
|
: {}),
|
|
@@ -1395,7 +1412,8 @@ export async function main() {
|
|
|
1395
1412
|
console.error(`[canon-codex] [${session.conversationId.slice(0, 8)}] Turn exited ${result.exitCode}: ${result.errorText}`);
|
|
1396
1413
|
}
|
|
1397
1414
|
const turnTrail = buildFinalTurnTrail(session);
|
|
1398
|
-
await client
|
|
1415
|
+
await sendMessageWithRetry(client, session.conversationId, userVisibleError, {
|
|
1416
|
+
messageId: buildCodexMessageId(session, 'error'),
|
|
1399
1417
|
...(session.activeSelfContextId
|
|
1400
1418
|
? { selfContextId: session.activeSelfContextId }
|
|
1401
1419
|
: {}),
|
|
@@ -1416,7 +1434,7 @@ export async function main() {
|
|
|
1416
1434
|
writeTurn(session);
|
|
1417
1435
|
stopVisibleWorkSignal(session);
|
|
1418
1436
|
clearStreaming(session.conversationId);
|
|
1419
|
-
|
|
1437
|
+
typingSignals.clear(session.conversationId).catch(() => { });
|
|
1420
1438
|
console.error(`[canon-codex] [${session.conversationId.slice(0, 8)}] Turn interrupted`);
|
|
1421
1439
|
}
|
|
1422
1440
|
}
|
|
@@ -1426,7 +1444,8 @@ export async function main() {
|
|
|
1426
1444
|
: `The Codex host failed to start a turn: ${error instanceof Error ? error.message : String(error)}`;
|
|
1427
1445
|
session.state.lastError = message;
|
|
1428
1446
|
writeState(session);
|
|
1429
|
-
await client
|
|
1447
|
+
await sendMessageWithRetry(client, session.conversationId, message, {
|
|
1448
|
+
messageId: buildCodexMessageId(session, 'failure'),
|
|
1430
1449
|
...(session.activeSelfContextId
|
|
1431
1450
|
? { selfContextId: session.activeSelfContextId }
|
|
1432
1451
|
: {}),
|
|
@@ -1777,6 +1796,8 @@ export async function main() {
|
|
|
1777
1796
|
});
|
|
1778
1797
|
const pollControl = async () => {
|
|
1779
1798
|
while (!controlStopped) {
|
|
1799
|
+
const hadActiveWork = [...sessions.values()].some((session) => !session.closed
|
|
1800
|
+
&& (session.running || session.queue.length > 0 || session.turnState === 'waiting_input'));
|
|
1780
1801
|
for (const conversationId of [...sessions.keys()]) {
|
|
1781
1802
|
try {
|
|
1782
1803
|
const controlRaw = await rtdbRead(`/control/${conversationId}/${agentId}/session`);
|
|
@@ -1845,14 +1866,14 @@ export async function main() {
|
|
|
1845
1866
|
session.turnState = 'interrupted';
|
|
1846
1867
|
writeTurn(session);
|
|
1847
1868
|
clearStreaming(conversationId);
|
|
1848
|
-
|
|
1869
|
+
typingSignals.clear(conversationId).catch(() => { });
|
|
1849
1870
|
await rtdbWrite(`/control/${conversationId}/${agentId}/signal`, null).catch(() => { });
|
|
1850
1871
|
}
|
|
1851
1872
|
catch {
|
|
1852
1873
|
// Ignore transient RTDB failures.
|
|
1853
1874
|
}
|
|
1854
1875
|
}
|
|
1855
|
-
await new Promise((resolve) => setTimeout(resolve,
|
|
1876
|
+
await new Promise((resolve) => setTimeout(resolve, controlPollDelayMs(hadActiveWork)));
|
|
1856
1877
|
}
|
|
1857
1878
|
};
|
|
1858
1879
|
void pollControl();
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@canonmsg/codex-plugin",
|
|
3
|
-
"version": "0.13.
|
|
3
|
+
"version": "0.13.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": "^2.1.
|
|
33
|
-
"@canonmsg/core": "^1.
|
|
32
|
+
"@canonmsg/agent-sdk": "^2.1.1",
|
|
33
|
+
"@canonmsg/core": "^1.3.0"
|
|
34
34
|
},
|
|
35
35
|
"engines": {
|
|
36
36
|
"node": ">=18.0.0"
|