@canonmsg/codex-plugin 0.21.0 → 0.22.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 +102 -146
- package/package.json +3 -3
package/dist/host.js
CHANGED
|
@@ -6,7 +6,7 @@ import { dirname } from 'node:path';
|
|
|
6
6
|
import { parseArgs } from 'node:util';
|
|
7
7
|
import { getCodexImagePath, materializeMessageMedia, materializeReplyContextMedia, sendMediaFileMessage, } from '@canonmsg/agent-sdk';
|
|
8
8
|
import { captureTurnArtifactSnapshot, collectTurnArtifacts, } from '@canonmsg/coding-agent-host';
|
|
9
|
-
import { RUNTIME_NEW_SESSION_ACTION, RUNTIME_STOP_ACTION, RUNTIME_STOP_AND_DROP_ACTION, buildRuntimeCardOutcome,
|
|
9
|
+
import { RUNTIME_NEW_SESSION_ACTION, RUNTIME_STOP_ACTION, RUNTIME_STOP_AND_DROP_ACTION, buildRuntimeCardOutcome, 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, markLocalRuntimeStopped, normalizeTurnMetadata, parseRuntimeCardV1, RuntimeRequestManager, prepareConversationEnvironment, loadHostSessionConfig, releaseConversationEnvironment, resolveCanonAgent, CanonApiError, sendMessageWithRetry, sendMessageWithRetryChunked, 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';
|
|
@@ -522,6 +522,58 @@ export async function main() {
|
|
|
522
522
|
}
|
|
523
523
|
console.error(`[canon-codex] Authenticated as ${agentId}`);
|
|
524
524
|
}
|
|
525
|
+
// Shared poll/timeout engine. Built-in `input`/`card` descriptors own
|
|
526
|
+
// create+poll (codex passes native/responder policy via payload/options).
|
|
527
|
+
// Approval keeps codex's own resolution shape (no owner-authored outcome
|
|
528
|
+
// messages / session rules — that is the SDK ApprovalManager's job).
|
|
529
|
+
const runtimeRequests = new RuntimeRequestManager(client, {
|
|
530
|
+
agentId,
|
|
531
|
+
ownerId: ownerId ?? '',
|
|
532
|
+
});
|
|
533
|
+
runtimeRequests.register('approval', {
|
|
534
|
+
kind: 'approval',
|
|
535
|
+
generateId: () => randomUUID(),
|
|
536
|
+
create: async ({ client: apiClient, ctx, conversationId, requestId, payload, expiresAt }) => {
|
|
537
|
+
await apiClient.createRuntimeApprovalRequest({
|
|
538
|
+
conversationId,
|
|
539
|
+
approvalId: requestId,
|
|
540
|
+
toolName: payload.toolName,
|
|
541
|
+
toolSummary: payload.toolSummary,
|
|
542
|
+
category: payload.category,
|
|
543
|
+
risk: payload.risk,
|
|
544
|
+
riskLevel: payload.riskLevel,
|
|
545
|
+
native: payload.native,
|
|
546
|
+
details: payload.details,
|
|
547
|
+
...(payload.diff ? { diff: payload.diff } : {}),
|
|
548
|
+
responseUserId: ctx.ownerId || undefined,
|
|
549
|
+
allowSessionRule: true,
|
|
550
|
+
expiresAt,
|
|
551
|
+
...(payload.turnId ? { turnId: payload.turnId } : {}),
|
|
552
|
+
});
|
|
553
|
+
return { requestId };
|
|
554
|
+
},
|
|
555
|
+
consume: async ({ client: apiClient, conversationId, requestId }) => {
|
|
556
|
+
const response = await apiClient
|
|
557
|
+
.consumeRuntimeApprovalResponse({ conversationId, approvalId: requestId })
|
|
558
|
+
.catch(() => null);
|
|
559
|
+
if (response?.status === 'allow') {
|
|
560
|
+
return {
|
|
561
|
+
state: 'resolved',
|
|
562
|
+
result: {
|
|
563
|
+
decision: 'allow',
|
|
564
|
+
...(response.sessionRule ? { sessionRule: response.sessionRule } : {}),
|
|
565
|
+
},
|
|
566
|
+
};
|
|
567
|
+
}
|
|
568
|
+
if (response?.status === 'deny') {
|
|
569
|
+
return { state: 'resolved', result: { decision: 'deny' } };
|
|
570
|
+
}
|
|
571
|
+
if (response?.status === 'timeout')
|
|
572
|
+
return { state: 'timeout' };
|
|
573
|
+
return { state: 'pending' };
|
|
574
|
+
},
|
|
575
|
+
timeoutResult: () => ({ decision: 'deny' }),
|
|
576
|
+
});
|
|
525
577
|
const launchArgs = [...process.argv.slice(2)];
|
|
526
578
|
if (!launchArgs.some((arg) => arg === '--cwd' || arg.startsWith('--cwd='))) {
|
|
527
579
|
launchArgs.push('--cwd', workingDir);
|
|
@@ -1015,80 +1067,6 @@ export async function main() {
|
|
|
1015
1067
|
? 'workspace-generated'
|
|
1016
1068
|
: 'disabled';
|
|
1017
1069
|
}
|
|
1018
|
-
async function waitForRuntimeInputResponse(input) {
|
|
1019
|
-
while (Date.now() < input.expiresAt) {
|
|
1020
|
-
const response = await client.consumeRuntimeInputResponse({
|
|
1021
|
-
conversationId: input.conversationId,
|
|
1022
|
-
inputId: input.inputId,
|
|
1023
|
-
}).catch(() => null);
|
|
1024
|
-
if (response?.status === 'submitted') {
|
|
1025
|
-
return { status: 'submitted', value: response.value, answers: response.answers };
|
|
1026
|
-
}
|
|
1027
|
-
if (response?.status === 'cancelled' || response?.status === 'timeout') {
|
|
1028
|
-
return { status: response.status };
|
|
1029
|
-
}
|
|
1030
|
-
await sleep(1_000);
|
|
1031
|
-
}
|
|
1032
|
-
const response = await client.consumeRuntimeInputResponse({
|
|
1033
|
-
conversationId: input.conversationId,
|
|
1034
|
-
inputId: input.inputId,
|
|
1035
|
-
}).catch(() => null);
|
|
1036
|
-
if (response?.status === 'submitted') {
|
|
1037
|
-
return { status: 'submitted', value: response.value, answers: response.answers };
|
|
1038
|
-
}
|
|
1039
|
-
return { status: 'timeout' };
|
|
1040
|
-
}
|
|
1041
|
-
async function waitForRuntimeApprovalResponse(input) {
|
|
1042
|
-
while (Date.now() < input.expiresAt) {
|
|
1043
|
-
const response = await client.consumeRuntimeApprovalResponse({
|
|
1044
|
-
conversationId: input.conversationId,
|
|
1045
|
-
approvalId: input.approvalId,
|
|
1046
|
-
}).catch(() => null);
|
|
1047
|
-
if (response?.status === 'allow') {
|
|
1048
|
-
return { decision: 'allow', sessionRule: response.sessionRule };
|
|
1049
|
-
}
|
|
1050
|
-
if (response?.status === 'deny' || response?.status === 'timeout') {
|
|
1051
|
-
return { decision: 'deny' };
|
|
1052
|
-
}
|
|
1053
|
-
await sleep(1_000);
|
|
1054
|
-
}
|
|
1055
|
-
await client.consumeRuntimeApprovalResponse({
|
|
1056
|
-
conversationId: input.conversationId,
|
|
1057
|
-
approvalId: input.approvalId,
|
|
1058
|
-
}).catch(() => null);
|
|
1059
|
-
return { decision: 'deny' };
|
|
1060
|
-
}
|
|
1061
|
-
async function waitForRuntimeCardResponse(input) {
|
|
1062
|
-
while (Date.now() < input.expiresAt) {
|
|
1063
|
-
const response = await client.consumeRuntimeCardResponse({
|
|
1064
|
-
conversationId: input.conversationId,
|
|
1065
|
-
cardId: input.cardId,
|
|
1066
|
-
}).catch(() => null);
|
|
1067
|
-
if (response?.status === 'submitted') {
|
|
1068
|
-
return {
|
|
1069
|
-
status: 'submitted',
|
|
1070
|
-
...(response.actionId ? { actionId: response.actionId } : {}),
|
|
1071
|
-
...(response.values ? { values: response.values } : {}),
|
|
1072
|
-
};
|
|
1073
|
-
}
|
|
1074
|
-
if (response?.status === 'cancelled' || response?.status === 'timeout') {
|
|
1075
|
-
return { status: response.status };
|
|
1076
|
-
}
|
|
1077
|
-
await sleep(1_000);
|
|
1078
|
-
}
|
|
1079
|
-
const response = await client.consumeRuntimeCardResponse({
|
|
1080
|
-
conversationId: input.conversationId,
|
|
1081
|
-
cardId: input.cardId,
|
|
1082
|
-
}).catch(() => null);
|
|
1083
|
-
if (response?.status === 'submitted') {
|
|
1084
|
-
return {
|
|
1085
|
-
status: 'submitted',
|
|
1086
|
-
...(response.actionId ? { actionId: response.actionId } : {}),
|
|
1087
|
-
...(response.values ? { values: response.values } : {}),
|
|
1088
|
-
};
|
|
1089
|
-
}
|
|
1090
|
-
return { status: 'timeout' };
|
|
1091
|
-
}
|
|
1092
1070
|
function runtimeCardRequestPayload(method, params) {
|
|
1093
1071
|
if (method !== 'item/runtimeCard/request'
|
|
1094
1072
|
&& method !== 'runtimeCard/request') {
|
|
@@ -1131,14 +1109,12 @@ export async function main() {
|
|
|
1131
1109
|
let requestCreated = false;
|
|
1132
1110
|
let requestResolved = false;
|
|
1133
1111
|
try {
|
|
1134
|
-
|
|
1135
|
-
|
|
1136
|
-
|
|
1137
|
-
|
|
1138
|
-
|
|
1139
|
-
|
|
1140
|
-
// owner if present, else the sole other member) — the owner is often
|
|
1141
|
-
// not a member of agent-to-user DMs.
|
|
1112
|
+
// Built-in card descriptor owns create+poll; codex passes native/turnId
|
|
1113
|
+
// via payload, post-create turn side effects via onCreated. responseUserId
|
|
1114
|
+
// is omitted (default `infer` policy) so the backend targets a reachable
|
|
1115
|
+
// member (owner is often not a member of agent-to-user DMs).
|
|
1116
|
+
const cardResult = await runtimeRequests.request('card', session.conversationId, {
|
|
1117
|
+
card,
|
|
1142
1118
|
native: {
|
|
1143
1119
|
runtime: 'codex',
|
|
1144
1120
|
method: request.method,
|
|
@@ -1150,25 +1126,32 @@ export async function main() {
|
|
|
1150
1126
|
},
|
|
1151
1127
|
},
|
|
1152
1128
|
turnId: session.currentTurnId ?? undefined,
|
|
1153
|
-
}
|
|
1154
|
-
|
|
1155
|
-
session.turnState = 'waiting_input';
|
|
1156
|
-
markTurnProgress(session);
|
|
1157
|
-
upsertTurnBlock(session, {
|
|
1158
|
-
id: `card:${cardId}`,
|
|
1159
|
-
kind: 'input',
|
|
1160
|
-
status: 'pending',
|
|
1161
|
-
title: card.title,
|
|
1162
|
-
summary: card.template ?? 'runtime card',
|
|
1163
|
-
});
|
|
1164
|
-
writeTurn(session);
|
|
1165
|
-
stopVisibleWorkSignal(session);
|
|
1166
|
-
writeCodexStreaming(session, null, 'waiting_input');
|
|
1167
|
-
const response = await waitForRuntimeCardResponse({
|
|
1168
|
-
conversationId: session.conversationId,
|
|
1169
|
-
cardId,
|
|
1129
|
+
}, {
|
|
1130
|
+
requestId: cardId,
|
|
1170
1131
|
expiresAt,
|
|
1132
|
+
onCreated: () => {
|
|
1133
|
+
requestCreated = true;
|
|
1134
|
+
session.turnState = 'waiting_input';
|
|
1135
|
+
markTurnProgress(session);
|
|
1136
|
+
upsertTurnBlock(session, {
|
|
1137
|
+
id: `card:${cardId}`,
|
|
1138
|
+
kind: 'input',
|
|
1139
|
+
status: 'pending',
|
|
1140
|
+
title: card.title,
|
|
1141
|
+
summary: card.template ?? 'runtime card',
|
|
1142
|
+
});
|
|
1143
|
+
writeTurn(session);
|
|
1144
|
+
stopVisibleWorkSignal(session);
|
|
1145
|
+
writeCodexStreaming(session, null, 'waiting_input');
|
|
1146
|
+
},
|
|
1171
1147
|
});
|
|
1148
|
+
const response = cardResult.status === 'submitted'
|
|
1149
|
+
? {
|
|
1150
|
+
status: 'submitted',
|
|
1151
|
+
...(cardResult.actionId ? { actionId: cardResult.actionId } : {}),
|
|
1152
|
+
...(cardResult.values ? { values: cardResult.values } : {}),
|
|
1153
|
+
}
|
|
1154
|
+
: { status: cardResult.status };
|
|
1172
1155
|
requestResolved = true;
|
|
1173
1156
|
const outcome = buildRuntimeCardOutcome(cardId, response.status, { reason: response.status });
|
|
1174
1157
|
await sendMessageWithRetry(client, session.conversationId, outcome.text, {
|
|
@@ -1216,12 +1199,10 @@ export async function main() {
|
|
|
1216
1199
|
const paramsArguments = isRecord(params.arguments) ? params.arguments : null;
|
|
1217
1200
|
const questions = mapCodexQuestions(params.questions ?? paramsInput?.questions ?? paramsArguments?.questions);
|
|
1218
1201
|
const inputId = readString(params, 'itemId') ?? requestId;
|
|
1219
|
-
|
|
1220
|
-
|
|
1221
|
-
|
|
1202
|
+
// Built-in input descriptor owns create+poll; default `owner` responder
|
|
1203
|
+
// policy resolves to ctx.ownerId (matching the former responseUserId).
|
|
1204
|
+
const response = await runtimeRequests.request('input', session.conversationId, {
|
|
1222
1205
|
kind: 'clarify',
|
|
1223
|
-
expiresAt,
|
|
1224
|
-
responseUserId: ownerId ?? undefined,
|
|
1225
1206
|
title: 'Codex needs input',
|
|
1226
1207
|
prompt: questions?.length
|
|
1227
1208
|
? 'Codex needs your input to continue.'
|
|
@@ -1239,12 +1220,7 @@ export async function main() {
|
|
|
1239
1220
|
},
|
|
1240
1221
|
},
|
|
1241
1222
|
turnId: session.currentTurnId ?? undefined,
|
|
1242
|
-
});
|
|
1243
|
-
const response = await waitForRuntimeInputResponse({
|
|
1244
|
-
conversationId: session.conversationId,
|
|
1245
|
-
inputId,
|
|
1246
|
-
expiresAt,
|
|
1247
|
-
});
|
|
1223
|
+
}, { requestId: inputId, expiresAt });
|
|
1248
1224
|
return { answers: response.status === 'submitted' ? response.answers ?? {} : {} };
|
|
1249
1225
|
}
|
|
1250
1226
|
const mappedApproval = mapCodexAppServerApprovalRequest({
|
|
@@ -1253,31 +1229,12 @@ export async function main() {
|
|
|
1253
1229
|
});
|
|
1254
1230
|
if (mappedApproval) {
|
|
1255
1231
|
const approvalId = readString(params, 'approvalId') ?? readString(params, 'itemId') ?? requestId;
|
|
1256
|
-
|
|
1257
|
-
|
|
1258
|
-
|
|
1259
|
-
|
|
1260
|
-
toolSummary: mappedApproval.toolSummary,
|
|
1261
|
-
category: mappedApproval.category,
|
|
1262
|
-
risk: mappedApproval.risk,
|
|
1263
|
-
riskLevel: mappedApproval.riskLevel,
|
|
1264
|
-
native: {
|
|
1265
|
-
...mappedApproval.native,
|
|
1266
|
-
requestId,
|
|
1267
|
-
method: request.method,
|
|
1268
|
-
},
|
|
1269
|
-
details: mappedApproval.details,
|
|
1270
|
-
...(mappedApproval.diff ? { diff: mappedApproval.diff } : {}),
|
|
1271
|
-
responseUserId: ownerId ?? undefined,
|
|
1272
|
-
allowSessionRule: true,
|
|
1273
|
-
expiresAt,
|
|
1232
|
+
// Approval descriptor's create authors the server request (responder = ctx.ownerId).
|
|
1233
|
+
const response = await runtimeRequests.request('approval', session.conversationId, {
|
|
1234
|
+
...mappedApproval,
|
|
1235
|
+
native: { ...mappedApproval.native, requestId, method: request.method },
|
|
1274
1236
|
turnId: session.currentTurnId ?? undefined,
|
|
1275
|
-
});
|
|
1276
|
-
const response = await waitForRuntimeApprovalResponse({
|
|
1277
|
-
conversationId: session.conversationId,
|
|
1278
|
-
approvalId,
|
|
1279
|
-
expiresAt,
|
|
1280
|
-
});
|
|
1237
|
+
}, { requestId: approvalId, expiresAt });
|
|
1281
1238
|
if (request.method === 'item/permissions/requestApproval') {
|
|
1282
1239
|
return response.decision === 'allow'
|
|
1283
1240
|
? { permissions: isRecord(params.permissions) ? params.permissions : {}, scope: response.sessionRule ? 'session' : 'turn' }
|
|
@@ -1628,19 +1585,17 @@ export async function main() {
|
|
|
1628
1585
|
}
|
|
1629
1586
|
if (!result.interrupted && result.finalMessage && nextTurn.planMode) {
|
|
1630
1587
|
await routeArtifactsOnce();
|
|
1631
|
-
|
|
1632
|
-
|
|
1588
|
+
// Route plan-CREATE through the unified spine (`/runtime-plan/request`):
|
|
1589
|
+
// seeds the server-owned pending node/attention/state and authors the
|
|
1590
|
+
// `plan_approval` card. Resolution is UNCHANGED — Codex still re-queues a
|
|
1591
|
+
// fresh turn off the server-authored `plan_approval_reply`.
|
|
1592
|
+
await client.createRuntimePlanRequest({
|
|
1593
|
+
conversationId: session.conversationId,
|
|
1594
|
+
planId: session.currentTurnId ?? randomUUID(),
|
|
1633
1595
|
title: 'Codex Plan',
|
|
1634
1596
|
body: result.finalMessage,
|
|
1635
|
-
|
|
1636
|
-
|
|
1637
|
-
messageId: buildCodexMessageId(session, 'plan'),
|
|
1638
|
-
metadata: {
|
|
1639
|
-
...planApproval.metadata,
|
|
1640
|
-
turnId: session.currentTurnId,
|
|
1641
|
-
turnSemantics: 'control',
|
|
1642
|
-
replyBehavior: 'suppress_auto_reply',
|
|
1643
|
-
},
|
|
1597
|
+
...(ownerId ? { responseUserId: ownerId } : {}),
|
|
1598
|
+
...(session.currentTurnId ? { turnId: session.currentTurnId } : {}),
|
|
1644
1599
|
});
|
|
1645
1600
|
await handoffFinalMessage(session.conversationId);
|
|
1646
1601
|
console.error(`[canon-codex] [${session.conversationId.slice(0, 8)}] Sent plan approval card`);
|
|
@@ -2136,6 +2091,7 @@ export async function main() {
|
|
|
2136
2091
|
controlPoller.stop();
|
|
2137
2092
|
clearInterval(heartbeat);
|
|
2138
2093
|
clearInterval(idleCheck);
|
|
2094
|
+
runtimeRequests.dispose();
|
|
2139
2095
|
stream.stop();
|
|
2140
2096
|
await runtimeState.clearAgentRuntime().catch(() => { });
|
|
2141
2097
|
for (const session of [...sessions.values()]) {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@canonmsg/codex-plugin",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.22.0",
|
|
4
4
|
"description": "Canon host integration for Codex CLI",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/index.js",
|
|
@@ -29,9 +29,9 @@
|
|
|
29
29
|
"prepack": "npm run build"
|
|
30
30
|
},
|
|
31
31
|
"dependencies": {
|
|
32
|
-
"@canonmsg/agent-sdk": "^5.
|
|
32
|
+
"@canonmsg/agent-sdk": "^5.1.0",
|
|
33
33
|
"@canonmsg/coding-agent-host": "^0.2.2",
|
|
34
|
-
"@canonmsg/core": "^4.
|
|
34
|
+
"@canonmsg/core": "^4.2.0"
|
|
35
35
|
},
|
|
36
36
|
"engines": {
|
|
37
37
|
"node": ">=18.0.0"
|