@canonmsg/codex-plugin 0.12.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 +191 -21
- 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, 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, 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 = [];
|
|
@@ -109,6 +115,21 @@ function buildCodexRuntimeDescriptor(input) {
|
|
|
109
115
|
presentation: input.presentation,
|
|
110
116
|
streamingTextMode: 'snapshot',
|
|
111
117
|
commands,
|
|
118
|
+
...(input.supportsRichCards
|
|
119
|
+
? {
|
|
120
|
+
runtimeCards: {
|
|
121
|
+
rich: {
|
|
122
|
+
schema: 'canon.card.v1',
|
|
123
|
+
lifecycle: 'blocking_requires_action',
|
|
124
|
+
responder: 'agent_owner',
|
|
125
|
+
result: 'action_or_values',
|
|
126
|
+
maxTimeoutMs: 30 * 60_000,
|
|
127
|
+
blockKinds: ['summary', 'metricGrid', 'chart', 'table', 'list', 'callout', 'actions'],
|
|
128
|
+
native: true,
|
|
129
|
+
},
|
|
130
|
+
},
|
|
131
|
+
}
|
|
132
|
+
: {}),
|
|
112
133
|
});
|
|
113
134
|
if (input.models.length > 0) {
|
|
114
135
|
return descriptor;
|
|
@@ -375,6 +396,11 @@ export async function main() {
|
|
|
375
396
|
console.error(`[canon-codex] Starting${profile ? ` (profile: ${profile})` : ''} in ${workingDir}`);
|
|
376
397
|
const client = new CanonClient(apiKey, baseUrl);
|
|
377
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
|
+
});
|
|
378
404
|
let agentId;
|
|
379
405
|
let ownerId = null;
|
|
380
406
|
let ownerName = null;
|
|
@@ -645,31 +671,33 @@ export async function main() {
|
|
|
645
671
|
turnId: session.currentTurnId ?? block.turnId,
|
|
646
672
|
})));
|
|
647
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
|
+
}
|
|
648
680
|
async function handoffFinalMessage(conversationId) {
|
|
649
681
|
await sleep(FINAL_MESSAGE_HANDOFF_MS);
|
|
650
682
|
clearStreaming(conversationId);
|
|
651
|
-
|
|
683
|
+
typingSignals.clear(conversationId).catch(() => { });
|
|
652
684
|
}
|
|
653
685
|
function refreshVisibleWorkSignal(session) {
|
|
654
686
|
if (!session.running || session.closed)
|
|
655
687
|
return;
|
|
656
688
|
if (session.turnState !== 'thinking' && session.turnState !== 'tool')
|
|
657
689
|
return;
|
|
658
|
-
|
|
690
|
+
typingSignals.start(session.conversationId, 'thinking').catch(() => { });
|
|
659
691
|
}
|
|
660
692
|
function startVisibleWorkSignal(session) {
|
|
661
693
|
refreshVisibleWorkSignal(session);
|
|
662
|
-
if (session.typingKeepaliveTimer)
|
|
663
|
-
return;
|
|
664
|
-
session.typingKeepaliveTimer = setInterval(() => {
|
|
665
|
-
refreshVisibleWorkSignal(session);
|
|
666
|
-
}, 3500);
|
|
667
694
|
}
|
|
668
695
|
function stopVisibleWorkSignal(session) {
|
|
669
696
|
if (session.typingKeepaliveTimer) {
|
|
670
697
|
clearInterval(session.typingKeepaliveTimer);
|
|
671
698
|
session.typingKeepaliveTimer = null;
|
|
672
699
|
}
|
|
700
|
+
typingSignals.clear(session.conversationId).catch(() => { });
|
|
673
701
|
}
|
|
674
702
|
function closeSession(conversationId) {
|
|
675
703
|
const session = sessions.get(conversationId);
|
|
@@ -684,7 +712,8 @@ export async function main() {
|
|
|
684
712
|
clearStreaming(conversationId);
|
|
685
713
|
runtimeState.clearSessionState(conversationId).catch(() => { });
|
|
686
714
|
runtimeState.clearTurnState(conversationId).catch(() => { });
|
|
687
|
-
|
|
715
|
+
typingSignals.clear(conversationId).catch(() => { });
|
|
716
|
+
typingSignals.dispose(conversationId);
|
|
688
717
|
sessions.delete(conversationId);
|
|
689
718
|
}
|
|
690
719
|
async function resetRuntimeSession(session) {
|
|
@@ -710,7 +739,7 @@ export async function main() {
|
|
|
710
739
|
}
|
|
711
740
|
stopVisibleWorkSignal(session);
|
|
712
741
|
clearStreaming(conversationId);
|
|
713
|
-
|
|
742
|
+
typingSignals.clear(conversationId).catch(() => { });
|
|
714
743
|
writeState(session);
|
|
715
744
|
writeTurn(session);
|
|
716
745
|
}
|
|
@@ -893,10 +922,143 @@ export async function main() {
|
|
|
893
922
|
}).catch(() => null);
|
|
894
923
|
return { decision: 'deny' };
|
|
895
924
|
}
|
|
925
|
+
async function waitForRuntimeCardResponse(input) {
|
|
926
|
+
while (Date.now() < input.expiresAt) {
|
|
927
|
+
const response = await client.consumeRuntimeCardResponse({
|
|
928
|
+
conversationId: input.conversationId,
|
|
929
|
+
cardId: input.cardId,
|
|
930
|
+
}).catch(() => null);
|
|
931
|
+
if (response?.status === 'submitted') {
|
|
932
|
+
return {
|
|
933
|
+
status: 'submitted',
|
|
934
|
+
...(response.actionId ? { actionId: response.actionId } : {}),
|
|
935
|
+
...(response.values ? { values: response.values } : {}),
|
|
936
|
+
};
|
|
937
|
+
}
|
|
938
|
+
if (response?.status === 'cancelled' || response?.status === 'timeout') {
|
|
939
|
+
return { status: response.status };
|
|
940
|
+
}
|
|
941
|
+
await sleep(1_000);
|
|
942
|
+
}
|
|
943
|
+
const response = await client.consumeRuntimeCardResponse({
|
|
944
|
+
conversationId: input.conversationId,
|
|
945
|
+
cardId: input.cardId,
|
|
946
|
+
}).catch(() => null);
|
|
947
|
+
if (response?.status === 'submitted') {
|
|
948
|
+
return {
|
|
949
|
+
status: 'submitted',
|
|
950
|
+
...(response.actionId ? { actionId: response.actionId } : {}),
|
|
951
|
+
...(response.values ? { values: response.values } : {}),
|
|
952
|
+
};
|
|
953
|
+
}
|
|
954
|
+
return { status: 'timeout' };
|
|
955
|
+
}
|
|
956
|
+
function runtimeCardRequestPayload(method, params) {
|
|
957
|
+
if (method !== 'item/runtimeCard/request'
|
|
958
|
+
&& method !== 'runtimeCard/request') {
|
|
959
|
+
return null;
|
|
960
|
+
}
|
|
961
|
+
const input = isRecord(params.input) ? params.input : null;
|
|
962
|
+
const args = isRecord(params.arguments) ? params.arguments : null;
|
|
963
|
+
return params.card ?? params.cardDocument ?? input?.card ?? args?.card ?? null;
|
|
964
|
+
}
|
|
896
965
|
async function handleCodexServerRequest(session, request) {
|
|
897
966
|
const requestId = String(request.id);
|
|
898
967
|
const params = request.params;
|
|
899
968
|
const expiresAt = Date.now() + 30 * 60_000;
|
|
969
|
+
const runtimeCardPayload = runtimeCardRequestPayload(request.method, params);
|
|
970
|
+
if (runtimeCardPayload) {
|
|
971
|
+
const card = parseRuntimeCardV1(runtimeCardPayload);
|
|
972
|
+
if (!card) {
|
|
973
|
+
return { status: 'cancelled', error: 'Invalid canon.card.v1 card' };
|
|
974
|
+
}
|
|
975
|
+
const cardId = readString(params, 'cardId')
|
|
976
|
+
?? readString(params, 'itemId')
|
|
977
|
+
?? card.cardId
|
|
978
|
+
?? requestId;
|
|
979
|
+
let requestCreated = false;
|
|
980
|
+
let requestResolved = false;
|
|
981
|
+
try {
|
|
982
|
+
await client.createRuntimeCardRequest({
|
|
983
|
+
conversationId: session.conversationId,
|
|
984
|
+
cardId,
|
|
985
|
+
card: { ...card, cardId },
|
|
986
|
+
expiresAt,
|
|
987
|
+
// Omit responseUserId so the backend targets a reachable member (the
|
|
988
|
+
// owner if present, else the sole other member) — the owner is often
|
|
989
|
+
// not a member of agent-to-user DMs.
|
|
990
|
+
native: {
|
|
991
|
+
runtime: 'codex',
|
|
992
|
+
method: request.method,
|
|
993
|
+
requestId,
|
|
994
|
+
turnId: readString(params, 'turnId') ?? session.currentTurnId ?? undefined,
|
|
995
|
+
handles: {
|
|
996
|
+
itemId: readString(params, 'itemId') ?? '',
|
|
997
|
+
threadId: readString(params, 'threadId') ?? '',
|
|
998
|
+
},
|
|
999
|
+
},
|
|
1000
|
+
turnId: session.currentTurnId ?? undefined,
|
|
1001
|
+
});
|
|
1002
|
+
requestCreated = true;
|
|
1003
|
+
session.turnState = 'waiting_input';
|
|
1004
|
+
markTurnProgress(session);
|
|
1005
|
+
upsertTurnBlock(session, {
|
|
1006
|
+
id: `card:${cardId}`,
|
|
1007
|
+
kind: 'input',
|
|
1008
|
+
status: 'pending',
|
|
1009
|
+
title: card.title,
|
|
1010
|
+
summary: card.template ?? 'runtime card',
|
|
1011
|
+
});
|
|
1012
|
+
writeTurn(session);
|
|
1013
|
+
stopVisibleWorkSignal(session);
|
|
1014
|
+
writeCodexStreaming(session, null, 'waiting_input');
|
|
1015
|
+
const response = await waitForRuntimeCardResponse({
|
|
1016
|
+
conversationId: session.conversationId,
|
|
1017
|
+
cardId,
|
|
1018
|
+
expiresAt,
|
|
1019
|
+
});
|
|
1020
|
+
requestResolved = true;
|
|
1021
|
+
const outcome = buildRuntimeCardOutcome(cardId, response.status, { reason: response.status });
|
|
1022
|
+
await sendMessageWithRetry(client, session.conversationId, outcome.text, {
|
|
1023
|
+
messageId: buildCodexRuntimeCardOutcomeMessageId(cardId, response.status),
|
|
1024
|
+
metadata: {
|
|
1025
|
+
...outcome.metadata,
|
|
1026
|
+
turnId: session.currentTurnId ?? undefined,
|
|
1027
|
+
turnSemantics: 'control',
|
|
1028
|
+
replyBehavior: 'suppress_auto_reply',
|
|
1029
|
+
},
|
|
1030
|
+
});
|
|
1031
|
+
completeTurnBlock(session, `card:${cardId}`, `Card ${response.status}`);
|
|
1032
|
+
if (session.turnState === 'waiting_input') {
|
|
1033
|
+
session.turnState = 'thinking';
|
|
1034
|
+
markTurnProgress(session);
|
|
1035
|
+
writeTurn(session);
|
|
1036
|
+
startVisibleWorkSignal(session);
|
|
1037
|
+
writeCodexStreaming(session, null, 'thinking');
|
|
1038
|
+
}
|
|
1039
|
+
return response;
|
|
1040
|
+
}
|
|
1041
|
+
catch (error) {
|
|
1042
|
+
if (requestCreated && !requestResolved) {
|
|
1043
|
+
await client.consumeRuntimeCardResponse({
|
|
1044
|
+
conversationId: session.conversationId,
|
|
1045
|
+
cardId,
|
|
1046
|
+
cancel: true,
|
|
1047
|
+
}).catch(() => null);
|
|
1048
|
+
const outcome = buildRuntimeCardOutcome(cardId, 'cancelled', { reason: 'interrupted' });
|
|
1049
|
+
await sendMessageWithRetry(client, session.conversationId, outcome.text, {
|
|
1050
|
+
messageId: buildCodexRuntimeCardOutcomeMessageId(cardId, 'cancelled'),
|
|
1051
|
+
metadata: {
|
|
1052
|
+
...outcome.metadata,
|
|
1053
|
+
turnId: session.currentTurnId ?? undefined,
|
|
1054
|
+
turnSemantics: 'control',
|
|
1055
|
+
replyBehavior: 'suppress_auto_reply',
|
|
1056
|
+
},
|
|
1057
|
+
}).catch(() => null);
|
|
1058
|
+
}
|
|
1059
|
+
throw error;
|
|
1060
|
+
}
|
|
1061
|
+
}
|
|
900
1062
|
if (request.method === 'item/tool/requestUserInput') {
|
|
901
1063
|
const paramsInput = isRecord(params.input) ? params.input : null;
|
|
902
1064
|
const paramsArguments = isRecord(params.arguments) ? params.arguments : null;
|
|
@@ -1051,7 +1213,8 @@ export async function main() {
|
|
|
1051
1213
|
const userMessage = error instanceof ExecutionEnvironmentError ? error.userMessage : message;
|
|
1052
1214
|
console.error(`[canon-codex] [${input.conversationId.slice(0, 8)}] Failed to create session: ${message}`);
|
|
1053
1215
|
await markQueuedMessageAccepted(input.conversationId, input.message.id, shouldMarkAccepted);
|
|
1054
|
-
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}`,
|
|
1055
1218
|
...(activeSelfContextId ? { selfContextId: activeSelfContextId } : {}),
|
|
1056
1219
|
metadata: {
|
|
1057
1220
|
turnSemantics: 'turn_complete',
|
|
@@ -1074,7 +1237,7 @@ export async function main() {
|
|
|
1074
1237
|
console.error(`[canon-codex] [${input.conversationId.slice(0, 8)}] Interrupting current turn for explicit human send-now`);
|
|
1075
1238
|
await session.adapter.interrupt().catch(() => { });
|
|
1076
1239
|
clearStreaming(input.conversationId);
|
|
1077
|
-
|
|
1240
|
+
typingSignals.clear(input.conversationId).catch(() => { });
|
|
1078
1241
|
return;
|
|
1079
1242
|
}
|
|
1080
1243
|
enqueuePrompt(session, prompt, deliveryIntent, false, input.message.id, shouldMarkAccepted, imagePaths, mediaAddDirs, planCommand.planMode);
|
|
@@ -1123,7 +1286,6 @@ export async function main() {
|
|
|
1123
1286
|
markTurnProgress(session);
|
|
1124
1287
|
writeTurn(session);
|
|
1125
1288
|
stopVisibleWorkSignal(session);
|
|
1126
|
-
client.setTyping(session.conversationId, false).catch(() => { });
|
|
1127
1289
|
writeCodexStreaming(session, event.text, 'streaming');
|
|
1128
1290
|
return;
|
|
1129
1291
|
}
|
|
@@ -1132,7 +1294,6 @@ export async function main() {
|
|
|
1132
1294
|
markTurnProgress(session);
|
|
1133
1295
|
writeTurn(session);
|
|
1134
1296
|
stopVisibleWorkSignal(session);
|
|
1135
|
-
client.setTyping(session.conversationId, false).catch(() => { });
|
|
1136
1297
|
upsertTurnBlock(session, {
|
|
1137
1298
|
id: `plan:${session.currentTurnId}`,
|
|
1138
1299
|
kind: 'plan',
|
|
@@ -1211,7 +1372,8 @@ export async function main() {
|
|
|
1211
1372
|
title: 'Codex Plan',
|
|
1212
1373
|
body: result.finalMessage,
|
|
1213
1374
|
});
|
|
1214
|
-
await client
|
|
1375
|
+
await sendMessageWithRetry(client, session.conversationId, planApproval.text, {
|
|
1376
|
+
messageId: buildCodexMessageId(session, 'plan'),
|
|
1215
1377
|
metadata: {
|
|
1216
1378
|
...planApproval.metadata,
|
|
1217
1379
|
turnId: session.currentTurnId,
|
|
@@ -1227,7 +1389,8 @@ export async function main() {
|
|
|
1227
1389
|
clearStoredThread();
|
|
1228
1390
|
}
|
|
1229
1391
|
const turnTrail = buildFinalTurnTrail(session);
|
|
1230
|
-
await client
|
|
1392
|
+
await sendMessageWithRetry(client, session.conversationId, result.finalMessage, {
|
|
1393
|
+
messageId: buildCodexMessageId(session, 'final'),
|
|
1231
1394
|
...(session.activeSelfContextId
|
|
1232
1395
|
? { selfContextId: session.activeSelfContextId }
|
|
1233
1396
|
: {}),
|
|
@@ -1249,7 +1412,8 @@ export async function main() {
|
|
|
1249
1412
|
console.error(`[canon-codex] [${session.conversationId.slice(0, 8)}] Turn exited ${result.exitCode}: ${result.errorText}`);
|
|
1250
1413
|
}
|
|
1251
1414
|
const turnTrail = buildFinalTurnTrail(session);
|
|
1252
|
-
await client
|
|
1415
|
+
await sendMessageWithRetry(client, session.conversationId, userVisibleError, {
|
|
1416
|
+
messageId: buildCodexMessageId(session, 'error'),
|
|
1253
1417
|
...(session.activeSelfContextId
|
|
1254
1418
|
? { selfContextId: session.activeSelfContextId }
|
|
1255
1419
|
: {}),
|
|
@@ -1270,7 +1434,7 @@ export async function main() {
|
|
|
1270
1434
|
writeTurn(session);
|
|
1271
1435
|
stopVisibleWorkSignal(session);
|
|
1272
1436
|
clearStreaming(session.conversationId);
|
|
1273
|
-
|
|
1437
|
+
typingSignals.clear(session.conversationId).catch(() => { });
|
|
1274
1438
|
console.error(`[canon-codex] [${session.conversationId.slice(0, 8)}] Turn interrupted`);
|
|
1275
1439
|
}
|
|
1276
1440
|
}
|
|
@@ -1280,7 +1444,8 @@ export async function main() {
|
|
|
1280
1444
|
: `The Codex host failed to start a turn: ${error instanceof Error ? error.message : String(error)}`;
|
|
1281
1445
|
session.state.lastError = message;
|
|
1282
1446
|
writeState(session);
|
|
1283
|
-
await client
|
|
1447
|
+
await sendMessageWithRetry(client, session.conversationId, message, {
|
|
1448
|
+
messageId: buildCodexMessageId(session, 'failure'),
|
|
1284
1449
|
...(session.activeSelfContextId
|
|
1285
1450
|
? { selfContextId: session.activeSelfContextId }
|
|
1286
1451
|
: {}),
|
|
@@ -1347,6 +1512,7 @@ export async function main() {
|
|
|
1347
1512
|
defaultPermissionMode: codexPermissionEnvelope.defaultPermissionMode,
|
|
1348
1513
|
presentation: runtimePresentation,
|
|
1349
1514
|
supportsPlanMode: useAppServer,
|
|
1515
|
+
supportsRichCards: useAppServer,
|
|
1350
1516
|
}),
|
|
1351
1517
|
};
|
|
1352
1518
|
async function baselineControlSignal(conversationId) {
|
|
@@ -1542,6 +1708,7 @@ export async function main() {
|
|
|
1542
1708
|
defaultPermissionMode: codexPermissionEnvelope.defaultPermissionMode,
|
|
1543
1709
|
presentation: runtimePresentation,
|
|
1544
1710
|
supportsPlanMode: useAppServer,
|
|
1711
|
+
supportsRichCards: useAppServer,
|
|
1545
1712
|
}),
|
|
1546
1713
|
};
|
|
1547
1714
|
}
|
|
@@ -1563,6 +1730,7 @@ export async function main() {
|
|
|
1563
1730
|
defaultPermissionMode: codexPermissionEnvelope.defaultPermissionMode,
|
|
1564
1731
|
presentation: runtimePresentation,
|
|
1565
1732
|
supportsPlanMode: useAppServer,
|
|
1733
|
+
supportsRichCards: useAppServer,
|
|
1566
1734
|
}),
|
|
1567
1735
|
};
|
|
1568
1736
|
}
|
|
@@ -1628,6 +1796,8 @@ export async function main() {
|
|
|
1628
1796
|
});
|
|
1629
1797
|
const pollControl = async () => {
|
|
1630
1798
|
while (!controlStopped) {
|
|
1799
|
+
const hadActiveWork = [...sessions.values()].some((session) => !session.closed
|
|
1800
|
+
&& (session.running || session.queue.length > 0 || session.turnState === 'waiting_input'));
|
|
1631
1801
|
for (const conversationId of [...sessions.keys()]) {
|
|
1632
1802
|
try {
|
|
1633
1803
|
const controlRaw = await rtdbRead(`/control/${conversationId}/${agentId}/session`);
|
|
@@ -1696,14 +1866,14 @@ export async function main() {
|
|
|
1696
1866
|
session.turnState = 'interrupted';
|
|
1697
1867
|
writeTurn(session);
|
|
1698
1868
|
clearStreaming(conversationId);
|
|
1699
|
-
|
|
1869
|
+
typingSignals.clear(conversationId).catch(() => { });
|
|
1700
1870
|
await rtdbWrite(`/control/${conversationId}/${agentId}/signal`, null).catch(() => { });
|
|
1701
1871
|
}
|
|
1702
1872
|
catch {
|
|
1703
1873
|
// Ignore transient RTDB failures.
|
|
1704
1874
|
}
|
|
1705
1875
|
}
|
|
1706
|
-
await new Promise((resolve) => setTimeout(resolve,
|
|
1876
|
+
await new Promise((resolve) => setTimeout(resolve, controlPollDelayMs(hadActiveWork)));
|
|
1707
1877
|
}
|
|
1708
1878
|
};
|
|
1709
1879
|
void pollControl();
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@canonmsg/codex-plugin",
|
|
3
|
-
"version": "0.
|
|
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.
|
|
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"
|