@canonmsg/codex-plugin 0.12.0 → 0.13.0
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 +150 -1
- 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, 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';
|
|
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';
|
|
@@ -109,6 +109,21 @@ function buildCodexRuntimeDescriptor(input) {
|
|
|
109
109
|
presentation: input.presentation,
|
|
110
110
|
streamingTextMode: 'snapshot',
|
|
111
111
|
commands,
|
|
112
|
+
...(input.supportsRichCards
|
|
113
|
+
? {
|
|
114
|
+
runtimeCards: {
|
|
115
|
+
rich: {
|
|
116
|
+
schema: 'canon.card.v1',
|
|
117
|
+
lifecycle: 'blocking_requires_action',
|
|
118
|
+
responder: 'agent_owner',
|
|
119
|
+
result: 'action_or_values',
|
|
120
|
+
maxTimeoutMs: 30 * 60_000,
|
|
121
|
+
blockKinds: ['summary', 'metricGrid', 'chart', 'table', 'list', 'callout', 'actions'],
|
|
122
|
+
native: true,
|
|
123
|
+
},
|
|
124
|
+
},
|
|
125
|
+
}
|
|
126
|
+
: {}),
|
|
112
127
|
});
|
|
113
128
|
if (input.models.length > 0) {
|
|
114
129
|
return descriptor;
|
|
@@ -893,10 +908,141 @@ export async function main() {
|
|
|
893
908
|
}).catch(() => null);
|
|
894
909
|
return { decision: 'deny' };
|
|
895
910
|
}
|
|
911
|
+
async function waitForRuntimeCardResponse(input) {
|
|
912
|
+
while (Date.now() < input.expiresAt) {
|
|
913
|
+
const response = await client.consumeRuntimeCardResponse({
|
|
914
|
+
conversationId: input.conversationId,
|
|
915
|
+
cardId: input.cardId,
|
|
916
|
+
}).catch(() => null);
|
|
917
|
+
if (response?.status === 'submitted') {
|
|
918
|
+
return {
|
|
919
|
+
status: 'submitted',
|
|
920
|
+
...(response.actionId ? { actionId: response.actionId } : {}),
|
|
921
|
+
...(response.values ? { values: response.values } : {}),
|
|
922
|
+
};
|
|
923
|
+
}
|
|
924
|
+
if (response?.status === 'cancelled' || response?.status === 'timeout') {
|
|
925
|
+
return { status: response.status };
|
|
926
|
+
}
|
|
927
|
+
await sleep(1_000);
|
|
928
|
+
}
|
|
929
|
+
const response = await client.consumeRuntimeCardResponse({
|
|
930
|
+
conversationId: input.conversationId,
|
|
931
|
+
cardId: input.cardId,
|
|
932
|
+
}).catch(() => null);
|
|
933
|
+
if (response?.status === 'submitted') {
|
|
934
|
+
return {
|
|
935
|
+
status: 'submitted',
|
|
936
|
+
...(response.actionId ? { actionId: response.actionId } : {}),
|
|
937
|
+
...(response.values ? { values: response.values } : {}),
|
|
938
|
+
};
|
|
939
|
+
}
|
|
940
|
+
return { status: 'timeout' };
|
|
941
|
+
}
|
|
942
|
+
function runtimeCardRequestPayload(method, params) {
|
|
943
|
+
if (method !== 'item/runtimeCard/request'
|
|
944
|
+
&& method !== 'runtimeCard/request') {
|
|
945
|
+
return null;
|
|
946
|
+
}
|
|
947
|
+
const input = isRecord(params.input) ? params.input : null;
|
|
948
|
+
const args = isRecord(params.arguments) ? params.arguments : null;
|
|
949
|
+
return params.card ?? params.cardDocument ?? input?.card ?? args?.card ?? null;
|
|
950
|
+
}
|
|
896
951
|
async function handleCodexServerRequest(session, request) {
|
|
897
952
|
const requestId = String(request.id);
|
|
898
953
|
const params = request.params;
|
|
899
954
|
const expiresAt = Date.now() + 30 * 60_000;
|
|
955
|
+
const runtimeCardPayload = runtimeCardRequestPayload(request.method, params);
|
|
956
|
+
if (runtimeCardPayload) {
|
|
957
|
+
const card = parseRuntimeCardV1(runtimeCardPayload);
|
|
958
|
+
if (!card) {
|
|
959
|
+
return { status: 'cancelled', error: 'Invalid canon.card.v1 card' };
|
|
960
|
+
}
|
|
961
|
+
const cardId = readString(params, 'cardId')
|
|
962
|
+
?? readString(params, 'itemId')
|
|
963
|
+
?? card.cardId
|
|
964
|
+
?? requestId;
|
|
965
|
+
let requestCreated = false;
|
|
966
|
+
let requestResolved = false;
|
|
967
|
+
try {
|
|
968
|
+
await client.createRuntimeCardRequest({
|
|
969
|
+
conversationId: session.conversationId,
|
|
970
|
+
cardId,
|
|
971
|
+
card: { ...card, cardId },
|
|
972
|
+
expiresAt,
|
|
973
|
+
// Omit responseUserId so the backend targets a reachable member (the
|
|
974
|
+
// owner if present, else the sole other member) — the owner is often
|
|
975
|
+
// not a member of agent-to-user DMs.
|
|
976
|
+
native: {
|
|
977
|
+
runtime: 'codex',
|
|
978
|
+
method: request.method,
|
|
979
|
+
requestId,
|
|
980
|
+
turnId: readString(params, 'turnId') ?? session.currentTurnId ?? undefined,
|
|
981
|
+
handles: {
|
|
982
|
+
itemId: readString(params, 'itemId') ?? '',
|
|
983
|
+
threadId: readString(params, 'threadId') ?? '',
|
|
984
|
+
},
|
|
985
|
+
},
|
|
986
|
+
turnId: session.currentTurnId ?? undefined,
|
|
987
|
+
});
|
|
988
|
+
requestCreated = true;
|
|
989
|
+
session.turnState = 'waiting_input';
|
|
990
|
+
markTurnProgress(session);
|
|
991
|
+
upsertTurnBlock(session, {
|
|
992
|
+
id: `card:${cardId}`,
|
|
993
|
+
kind: 'input',
|
|
994
|
+
status: 'pending',
|
|
995
|
+
title: card.title,
|
|
996
|
+
summary: card.template ?? 'runtime card',
|
|
997
|
+
});
|
|
998
|
+
writeTurn(session);
|
|
999
|
+
stopVisibleWorkSignal(session);
|
|
1000
|
+
writeCodexStreaming(session, null, 'waiting_input');
|
|
1001
|
+
const response = await waitForRuntimeCardResponse({
|
|
1002
|
+
conversationId: session.conversationId,
|
|
1003
|
+
cardId,
|
|
1004
|
+
expiresAt,
|
|
1005
|
+
});
|
|
1006
|
+
requestResolved = true;
|
|
1007
|
+
const outcome = buildRuntimeCardOutcome(cardId, response.status, { reason: response.status });
|
|
1008
|
+
await client.sendMessage(session.conversationId, outcome.text, {
|
|
1009
|
+
metadata: {
|
|
1010
|
+
...outcome.metadata,
|
|
1011
|
+
turnId: session.currentTurnId ?? undefined,
|
|
1012
|
+
turnSemantics: 'control',
|
|
1013
|
+
replyBehavior: 'suppress_auto_reply',
|
|
1014
|
+
},
|
|
1015
|
+
});
|
|
1016
|
+
completeTurnBlock(session, `card:${cardId}`, `Card ${response.status}`);
|
|
1017
|
+
if (session.turnState === 'waiting_input') {
|
|
1018
|
+
session.turnState = 'thinking';
|
|
1019
|
+
markTurnProgress(session);
|
|
1020
|
+
writeTurn(session);
|
|
1021
|
+
startVisibleWorkSignal(session);
|
|
1022
|
+
writeCodexStreaming(session, null, 'thinking');
|
|
1023
|
+
}
|
|
1024
|
+
return response;
|
|
1025
|
+
}
|
|
1026
|
+
catch (error) {
|
|
1027
|
+
if (requestCreated && !requestResolved) {
|
|
1028
|
+
await client.consumeRuntimeCardResponse({
|
|
1029
|
+
conversationId: session.conversationId,
|
|
1030
|
+
cardId,
|
|
1031
|
+
cancel: true,
|
|
1032
|
+
}).catch(() => null);
|
|
1033
|
+
const outcome = buildRuntimeCardOutcome(cardId, 'cancelled', { reason: 'interrupted' });
|
|
1034
|
+
await client.sendMessage(session.conversationId, outcome.text, {
|
|
1035
|
+
metadata: {
|
|
1036
|
+
...outcome.metadata,
|
|
1037
|
+
turnId: session.currentTurnId ?? undefined,
|
|
1038
|
+
turnSemantics: 'control',
|
|
1039
|
+
replyBehavior: 'suppress_auto_reply',
|
|
1040
|
+
},
|
|
1041
|
+
}).catch(() => null);
|
|
1042
|
+
}
|
|
1043
|
+
throw error;
|
|
1044
|
+
}
|
|
1045
|
+
}
|
|
900
1046
|
if (request.method === 'item/tool/requestUserInput') {
|
|
901
1047
|
const paramsInput = isRecord(params.input) ? params.input : null;
|
|
902
1048
|
const paramsArguments = isRecord(params.arguments) ? params.arguments : null;
|
|
@@ -1347,6 +1493,7 @@ export async function main() {
|
|
|
1347
1493
|
defaultPermissionMode: codexPermissionEnvelope.defaultPermissionMode,
|
|
1348
1494
|
presentation: runtimePresentation,
|
|
1349
1495
|
supportsPlanMode: useAppServer,
|
|
1496
|
+
supportsRichCards: useAppServer,
|
|
1350
1497
|
}),
|
|
1351
1498
|
};
|
|
1352
1499
|
async function baselineControlSignal(conversationId) {
|
|
@@ -1542,6 +1689,7 @@ export async function main() {
|
|
|
1542
1689
|
defaultPermissionMode: codexPermissionEnvelope.defaultPermissionMode,
|
|
1543
1690
|
presentation: runtimePresentation,
|
|
1544
1691
|
supportsPlanMode: useAppServer,
|
|
1692
|
+
supportsRichCards: useAppServer,
|
|
1545
1693
|
}),
|
|
1546
1694
|
};
|
|
1547
1695
|
}
|
|
@@ -1563,6 +1711,7 @@ export async function main() {
|
|
|
1563
1711
|
defaultPermissionMode: codexPermissionEnvelope.defaultPermissionMode,
|
|
1564
1712
|
presentation: runtimePresentation,
|
|
1565
1713
|
supportsPlanMode: useAppServer,
|
|
1714
|
+
supportsRichCards: useAppServer,
|
|
1566
1715
|
}),
|
|
1567
1716
|
};
|
|
1568
1717
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@canonmsg/codex-plugin",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.13.0",
|
|
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.0",
|
|
33
|
+
"@canonmsg/core": "^1.2.0"
|
|
34
34
|
},
|
|
35
35
|
"engines": {
|
|
36
36
|
"node": ">=18.0.0"
|