@canonmsg/codex-plugin 0.27.0 → 0.28.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/README.md +0 -17
- package/dist/app-server-adapter.js +1 -7
- package/dist/codex-app-tools.d.ts +6 -26
- package/dist/codex-app-tools.js +27 -83
- package/dist/host.d.ts +2 -2
- package/dist/host.js +39 -123
- package/dist/register.js +0 -1
- package/package.json +6 -6
package/README.md
CHANGED
|
@@ -109,23 +109,6 @@ discovery it also offers no model picker and a fixed effort list. It registers
|
|
|
109
109
|
no dynamic tools either, so `no_reply` is unavailable there, the same way rich
|
|
110
110
|
cards are.
|
|
111
111
|
|
|
112
|
-
On the app-server transport, an owner-started foreground turn also receives a
|
|
113
|
-
small Canon communication surface: `codex_app.canon_send_to`, contact/request
|
|
114
|
-
and conversation listing, and `codex_app.canon_cancel_contact_request` for the
|
|
115
|
-
agent's own pending DM requests. `canon_send_to` accepts one stable contact
|
|
116
|
-
target and visible text; the host supplies trusted source-turn routing. These
|
|
117
|
-
tools are withheld from non-owner turns, service-agent mode, detached threads,
|
|
118
|
-
and the exec fallback. Contact cards only claim reach-out support when this
|
|
119
|
-
exact surface is mounted.
|
|
120
|
-
|
|
121
|
-
When a reach-out needs target-owner setup or approval, Canon parks the visible
|
|
122
|
-
opener. Terminal request outcomes enter a deduplicated local lifecycle inbox and
|
|
123
|
-
are supplied as trusted context on the next natural owner turn; they never start
|
|
124
|
-
a synthetic model turn. A connected outcome means Canon already delivered the
|
|
125
|
-
opener. After an SSE reconnect or expired replay window, the host refreshes
|
|
126
|
-
memberships, reconciles outbound request phases, and recovers unseen messages
|
|
127
|
-
from its persisted cursors before resuming live delivery.
|
|
128
|
-
|
|
129
112
|
Set `CANON_CODEX_TRANSPORT=exec` or `CANON_CODEX_TRANSPORT=app-server` to skip
|
|
130
113
|
the probe when it misfires.
|
|
131
114
|
|
|
@@ -638,13 +638,7 @@ function isForegroundCanonToolCall(params) {
|
|
|
638
638
|
const tool = rawTool.startsWith('codex_app.')
|
|
639
639
|
? rawTool.slice('codex_app.'.length)
|
|
640
640
|
: rawTool;
|
|
641
|
-
return tool === 'canon_runtime_control'
|
|
642
|
-
|| tool === 'canon_send_to'
|
|
643
|
-
|| tool === 'canon_list_contacts'
|
|
644
|
-
|| tool === 'canon_list_contact_requests'
|
|
645
|
-
|| tool === 'canon_list_conversations'
|
|
646
|
-
|| tool === 'canon_cancel_contact_request'
|
|
647
|
-
|| tool === 'no_reply';
|
|
641
|
+
return tool === 'canon_runtime_control' || tool === 'no_reply';
|
|
648
642
|
}
|
|
649
643
|
function parseJson(line) {
|
|
650
644
|
try {
|
|
@@ -1,5 +1,4 @@
|
|
|
1
|
-
import { type CanonClient, type
|
|
2
|
-
import { type OwnerBoundCanonContactTarget } from '@canonmsg/agent-tools';
|
|
1
|
+
import { type CanonClient, type NoReplyReportClient } from '@canonmsg/core';
|
|
3
2
|
type JsonRecord = Record<string, unknown>;
|
|
4
3
|
interface DynamicToolSpec {
|
|
5
4
|
[key: string]: unknown;
|
|
@@ -45,11 +44,7 @@ type DynamicToolCallResponse = {
|
|
|
45
44
|
*/
|
|
46
45
|
export declare const CODEX_NO_REPLY_TOOL_NAME = "no_reply";
|
|
47
46
|
export declare const CANON_RUNTIME_CONTROL_TOOL_NAME = "canon_runtime_control";
|
|
48
|
-
export declare const
|
|
49
|
-
export declare const CODEX_CANON_LIST_CONTACTS_TOOL_NAME = "canon_list_contacts";
|
|
50
|
-
export declare const CODEX_CANON_LIST_CONTACT_REQUESTS_TOOL_NAME = "canon_list_contact_requests";
|
|
51
|
-
export declare const CODEX_CANON_LIST_CONVERSATIONS_TOOL_NAME = "canon_list_conversations";
|
|
52
|
-
export declare const CODEX_CANON_CANCEL_CONTACT_REQUEST_TOOL_NAME = "canon_cancel_contact_request";
|
|
47
|
+
export declare const CODEX_COMMUNICATE_TOOL_NAME = "communicate";
|
|
53
48
|
/**
|
|
54
49
|
* What the model actually sees for the tool above, for prompt text that names
|
|
55
50
|
* it (the group posture cue). Only meaningful on the app-server transport —
|
|
@@ -59,6 +54,8 @@ export declare const CODEX_NO_REPLY_MODEL_TOOL_NAME = "codex_app.no_reply";
|
|
|
59
54
|
export declare const CODEX_APP_DYNAMIC_TOOLS: ReadonlyArray<DynamicToolSpec>;
|
|
60
55
|
/** The complete model-visible surface for non-coding service agents. */
|
|
61
56
|
export declare const CODEX_SERVICE_AGENT_DYNAMIC_TOOLS: ReadonlyArray<DynamicToolSpec>;
|
|
57
|
+
/** Closed agents are not shown the optional outbound communication surface. */
|
|
58
|
+
export declare function filterCodexCommunicationTools(tools: ReadonlyArray<DynamicToolSpec>, outboundPolicy: 'open' | 'approval-required' | 'closed' | null): ReadonlyArray<DynamicToolSpec>;
|
|
62
59
|
/**
|
|
63
60
|
* Tools exposed to detached Codex threads created through the bridge. Canon
|
|
64
61
|
* conversation controls belong only to the foreground turn that received the
|
|
@@ -71,26 +68,9 @@ export declare function successfulCodexAppToolResult(payload: unknown): DynamicT
|
|
|
71
68
|
/** True when this dynamic-tool call is the deliberate-silence verb. */
|
|
72
69
|
export declare function isCodexNoReplyToolCall(params: CodexAppToolCallParams): boolean;
|
|
73
70
|
export declare function isCanonRuntimeControlToolCall(params: CodexAppToolCallParams): boolean;
|
|
74
|
-
export declare function
|
|
75
|
-
export declare function isCodexCanonVerbToolCall(params: CodexAppToolCallParams): boolean;
|
|
71
|
+
export declare function isCodexCommunicateToolCall(params: CodexAppToolCallParams): boolean;
|
|
76
72
|
export declare function isCodexServiceAgentToolCall(params: CodexAppToolCallParams): boolean;
|
|
77
|
-
export
|
|
78
|
-
conversationId: string;
|
|
79
|
-
sourceMessageId: string | null;
|
|
80
|
-
turnId: string | null;
|
|
81
|
-
replyContactTarget?: OwnerBoundCanonContactTarget;
|
|
82
|
-
}
|
|
83
|
-
/**
|
|
84
|
-
* Execute the deliberately filtered canonical-verb projection. The model can
|
|
85
|
-
* author only the destination and visible copy. Provenance and turn metadata
|
|
86
|
-
* come exclusively from the active host turn, so a prompt cannot forge its
|
|
87
|
-
* source or make an outbound message look like an unrelated event.
|
|
88
|
-
*/
|
|
89
|
-
export declare function answerCodexCanonVerb(input: {
|
|
90
|
-
client: CanonClient;
|
|
91
|
-
params: CodexAppToolCallParams;
|
|
92
|
-
context: CodexCanonVerbTrustedContext;
|
|
93
|
-
}): Promise<DynamicToolCallResponse>;
|
|
73
|
+
export declare function handleCodexCommunicateToolCall(client: CanonClient, params: CodexAppToolCallParams): Promise<DynamicToolCallResponse>;
|
|
94
74
|
export interface CanonRuntimeControlRequest {
|
|
95
75
|
action: 'send_card' | 'request_card' | 'request_input';
|
|
96
76
|
card?: unknown;
|
package/dist/codex-app-tools.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { NO_REPLY_ACK_NOTE, reportNoReplyOutcome, } from '@canonmsg/core';
|
|
2
|
-
import {
|
|
2
|
+
import { canonCommunicateToolDefinition, createCanonCommunicationBinding, } from '@canonmsg/agent-tools';
|
|
3
3
|
const emptyObjectSchema = {
|
|
4
4
|
type: 'object',
|
|
5
5
|
properties: {},
|
|
@@ -91,21 +91,7 @@ function tool(name, description, inputSchema, deferLoading = true) {
|
|
|
91
91
|
*/
|
|
92
92
|
export const CODEX_NO_REPLY_TOOL_NAME = 'no_reply';
|
|
93
93
|
export const CANON_RUNTIME_CONTROL_TOOL_NAME = 'canon_runtime_control';
|
|
94
|
-
export const
|
|
95
|
-
export const CODEX_CANON_LIST_CONTACTS_TOOL_NAME = 'canon_list_contacts';
|
|
96
|
-
export const CODEX_CANON_LIST_CONTACT_REQUESTS_TOOL_NAME = 'canon_list_contact_requests';
|
|
97
|
-
export const CODEX_CANON_LIST_CONVERSATIONS_TOOL_NAME = 'canon_list_conversations';
|
|
98
|
-
export const CODEX_CANON_CANCEL_CONTACT_REQUEST_TOOL_NAME = 'canon_cancel_contact_request';
|
|
99
|
-
const CODEX_CANON_COMMUNICATION_BINDING = createOwnerBoundCanonCommunicationBinding({
|
|
100
|
-
toolNames: {
|
|
101
|
-
send_to: CODEX_CANON_SEND_TO_TOOL_NAME,
|
|
102
|
-
list_contacts: CODEX_CANON_LIST_CONTACTS_TOOL_NAME,
|
|
103
|
-
list_contact_requests: CODEX_CANON_LIST_CONTACT_REQUESTS_TOOL_NAME,
|
|
104
|
-
list_conversations: CODEX_CANON_LIST_CONVERSATIONS_TOOL_NAME,
|
|
105
|
-
cancel_contact_request: CODEX_CANON_CANCEL_CONTACT_REQUEST_TOOL_NAME,
|
|
106
|
-
},
|
|
107
|
-
});
|
|
108
|
-
const CODEX_CANON_FOREGROUND_TOOL_NAMES = new Set(CODEX_CANON_COMMUNICATION_BINDING.tools.map((definition) => definition.name));
|
|
94
|
+
export const CODEX_COMMUNICATE_TOOL_NAME = 'communicate';
|
|
109
95
|
/**
|
|
110
96
|
* What the model actually sees for the tool above, for prompt text that names
|
|
111
97
|
* it (the group posture cue). Only meaningful on the app-server transport —
|
|
@@ -137,7 +123,11 @@ const CANON_RUNTIME_CONTROL_TOOL = tool(CANON_RUNTIME_CONTROL_TOOL_NAME, 'Intera
|
|
|
137
123
|
},
|
|
138
124
|
required: ['action'],
|
|
139
125
|
}, false);
|
|
140
|
-
const
|
|
126
|
+
const communicationBinding = createCanonCommunicationBinding({
|
|
127
|
+
toolName: CODEX_COMMUNICATE_TOOL_NAME,
|
|
128
|
+
});
|
|
129
|
+
const communicateDefinition = canonCommunicateToolDefinition(CODEX_COMMUNICATE_TOOL_NAME);
|
|
130
|
+
const CODEX_COMMUNICATE_TOOL = tool(communicateDefinition.name, communicateDefinition.description, communicateDefinition.inputSchema, false);
|
|
141
131
|
const CODEX_NO_REPLY_TOOL = tool(CODEX_NO_REPLY_TOOL_NAME, 'End your turn without posting anything to the conversation. Use it in group '
|
|
142
132
|
+ 'chats when you have nothing to add — no message is created, so no other '
|
|
143
133
|
+ 'member or agent is triggered. Optional private reason (logged, never '
|
|
@@ -261,8 +251,8 @@ export const CODEX_APP_DYNAMIC_TOOLS = [
|
|
|
261
251
|
},
|
|
262
252
|
required: ['threadId', 'title'],
|
|
263
253
|
}),
|
|
264
|
-
...CODEX_CANON_COMMUNICATION_TOOLS,
|
|
265
254
|
CANON_RUNTIME_CONTROL_TOOL,
|
|
255
|
+
CODEX_COMMUNICATE_TOOL,
|
|
266
256
|
// Canon's `no_reply` verb, projected into the only model-visible tool surface
|
|
267
257
|
// the Codex transport gives us. It is answered by the Canon host itself.
|
|
268
258
|
CODEX_NO_REPLY_TOOL,
|
|
@@ -270,16 +260,22 @@ export const CODEX_APP_DYNAMIC_TOOLS = [
|
|
|
270
260
|
/** The complete model-visible surface for non-coding service agents. */
|
|
271
261
|
export const CODEX_SERVICE_AGENT_DYNAMIC_TOOLS = [
|
|
272
262
|
CANON_RUNTIME_CONTROL_TOOL,
|
|
263
|
+
CODEX_COMMUNICATE_TOOL,
|
|
273
264
|
CODEX_NO_REPLY_TOOL,
|
|
274
265
|
];
|
|
266
|
+
/** Closed agents are not shown the optional outbound communication surface. */
|
|
267
|
+
export function filterCodexCommunicationTools(tools, outboundPolicy) {
|
|
268
|
+
return outboundPolicy === 'closed'
|
|
269
|
+
? tools.filter((entry) => entry.name !== CODEX_COMMUNICATE_TOOL_NAME)
|
|
270
|
+
: tools;
|
|
271
|
+
}
|
|
275
272
|
/**
|
|
276
273
|
* Tools exposed to detached Codex threads created through the bridge. Canon
|
|
277
274
|
* conversation controls belong only to the foreground turn that received the
|
|
278
275
|
* human message; a child thread has neither that turn nor its responder.
|
|
279
276
|
*/
|
|
280
277
|
export const CODEX_DETACHED_THREAD_DYNAMIC_TOOLS = CODEX_APP_DYNAMIC_TOOLS.filter((entry) => (entry.name !== CANON_RUNTIME_CONTROL_TOOL_NAME
|
|
281
|
-
&& entry.name !== CODEX_NO_REPLY_TOOL_NAME
|
|
282
|
-
&& !CODEX_CANON_FOREGROUND_TOOL_NAMES.has(entry.name)));
|
|
278
|
+
&& entry.name !== CODEX_NO_REPLY_TOOL_NAME));
|
|
283
279
|
const CODEX_APP_TOOL_NAMES = new Set(CODEX_APP_DYNAMIC_TOOLS.map((entry) => String(entry.name)));
|
|
284
280
|
const UNSUPPORTED_TOOLS = new Map([
|
|
285
281
|
['automation_update', 'Canon does not manage Codex Desktop automations.'],
|
|
@@ -313,52 +309,21 @@ export function isCodexNoReplyToolCall(params) {
|
|
|
313
309
|
export function isCanonRuntimeControlToolCall(params) {
|
|
314
310
|
return normalizeToolName(params.tool) === CANON_RUNTIME_CONTROL_TOOL_NAME;
|
|
315
311
|
}
|
|
316
|
-
export function
|
|
317
|
-
|
|
318
|
-
return toolName ? CODEX_CANON_COMMUNICATION_BINDING.verbForToolName(toolName) : null;
|
|
319
|
-
}
|
|
320
|
-
export function isCodexCanonVerbToolCall(params) {
|
|
321
|
-
return codexCanonVerbForToolCall(params) !== null;
|
|
312
|
+
export function isCodexCommunicateToolCall(params) {
|
|
313
|
+
return normalizeToolName(params.tool) === CODEX_COMMUNICATE_TOOL_NAME;
|
|
322
314
|
}
|
|
323
315
|
export function isCodexServiceAgentToolCall(params) {
|
|
324
316
|
const toolName = normalizeToolName(params.tool);
|
|
325
|
-
return toolName === CANON_RUNTIME_CONTROL_TOOL_NAME
|
|
317
|
+
return toolName === CANON_RUNTIME_CONTROL_TOOL_NAME
|
|
318
|
+
|| toolName === CODEX_COMMUNICATE_TOOL_NAME
|
|
319
|
+
|| toolName === CODEX_NO_REPLY_TOOL_NAME;
|
|
326
320
|
}
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
export async function answerCodexCanonVerb(input) {
|
|
334
|
-
const verb = codexCanonVerbForToolCall(input.params);
|
|
335
|
-
if (!verb)
|
|
336
|
-
return toolResult(false, { error: 'Unsupported Canon verb tool.' });
|
|
337
|
-
try {
|
|
338
|
-
const result = await CODEX_CANON_COMMUNICATION_BINDING.execute({
|
|
339
|
-
client: input.client,
|
|
340
|
-
toolName: normalizeToolName(input.params.tool) ?? '',
|
|
341
|
-
arguments: input.params.arguments,
|
|
342
|
-
context: input.context.sourceMessageId ? {
|
|
343
|
-
isOwnerTurn: true,
|
|
344
|
-
conversationId: input.context.conversationId,
|
|
345
|
-
sourceMessageId: input.context.sourceMessageId,
|
|
346
|
-
...(input.context.turnId ? { turnId: input.context.turnId } : {}),
|
|
347
|
-
...(input.context.replyContactTarget
|
|
348
|
-
? { replyContactTarget: input.context.replyContactTarget }
|
|
349
|
-
: {}),
|
|
350
|
-
} : null,
|
|
351
|
-
});
|
|
352
|
-
return {
|
|
353
|
-
success: result.isError !== true,
|
|
354
|
-
contentItems: result.content.map((item) => ({ type: 'inputText', text: item.text })),
|
|
355
|
-
};
|
|
356
|
-
}
|
|
357
|
-
catch (error) {
|
|
358
|
-
return toolResult(false, {
|
|
359
|
-
error: error instanceof Error ? error.message : String(error),
|
|
360
|
-
});
|
|
361
|
-
}
|
|
321
|
+
export async function handleCodexCommunicateToolCall(client, params) {
|
|
322
|
+
const result = await communicationBinding.execute(client, CODEX_COMMUNICATE_TOOL_NAME, parseToolArguments(params.arguments));
|
|
323
|
+
return {
|
|
324
|
+
success: result.isError !== true,
|
|
325
|
+
contentItems: result.content.map((item) => ({ type: 'inputText', text: item.text })),
|
|
326
|
+
};
|
|
362
327
|
}
|
|
363
328
|
export function parseCanonRuntimeControlRequest(params) {
|
|
364
329
|
if (!isCanonRuntimeControlToolCall(params))
|
|
@@ -448,12 +413,6 @@ export async function handleCodexAppToolCall(runtime, params) {
|
|
|
448
413
|
error: 'canon_runtime_control is answered by the Canon host, not the app-tool bridge.',
|
|
449
414
|
});
|
|
450
415
|
}
|
|
451
|
-
if (CODEX_CANON_FOREGROUND_TOOL_NAMES.has(toolName)) {
|
|
452
|
-
return toolResult(false, {
|
|
453
|
-
tool: toolName,
|
|
454
|
-
error: `${toolName} is answered by the Canon host, not the app-tool bridge.`,
|
|
455
|
-
});
|
|
456
|
-
}
|
|
457
416
|
const unsupportedReason = UNSUPPORTED_TOOLS.get(toolName);
|
|
458
417
|
if (unsupportedReason) {
|
|
459
418
|
return toolResult(false, { tool: toolName, error: unsupportedReason });
|
|
@@ -702,21 +661,6 @@ function parseToolArguments(value) {
|
|
|
702
661
|
}
|
|
703
662
|
return {};
|
|
704
663
|
}
|
|
705
|
-
function parseToolArgumentsStrict(value) {
|
|
706
|
-
if (value === undefined || value === null || value === '')
|
|
707
|
-
return {};
|
|
708
|
-
if (isRecord(value))
|
|
709
|
-
return value;
|
|
710
|
-
if (typeof value !== 'string' || !value.trim())
|
|
711
|
-
return null;
|
|
712
|
-
try {
|
|
713
|
-
const parsed = JSON.parse(value);
|
|
714
|
-
return isRecord(parsed) ? parsed : null;
|
|
715
|
-
}
|
|
716
|
-
catch {
|
|
717
|
-
return null;
|
|
718
|
-
}
|
|
719
|
-
}
|
|
720
664
|
function normalizeToolName(value) {
|
|
721
665
|
if (typeof value !== 'string' || !value.trim())
|
|
722
666
|
return null;
|
package/dist/host.d.ts
CHANGED
|
@@ -61,6 +61,7 @@ export declare function buildCodexRuntimeDescriptor(input: {
|
|
|
61
61
|
workspaces: WorkspaceOption[];
|
|
62
62
|
workspaceRoots?: CanonWorkspaceRootMetadata[];
|
|
63
63
|
executionModes: ExecutionEnvironmentMode[];
|
|
64
|
+
defaultExecutionMode?: ExecutionEnvironmentMode;
|
|
64
65
|
permissionModes: Array<{
|
|
65
66
|
value: string;
|
|
66
67
|
label: string;
|
|
@@ -70,7 +71,6 @@ export declare function buildCodexRuntimeDescriptor(input: {
|
|
|
70
71
|
supportsPlanMode: boolean;
|
|
71
72
|
supportsCompact?: boolean;
|
|
72
73
|
supportsRichCards?: boolean;
|
|
73
|
-
supportsCanonCommunicationTools?: boolean;
|
|
74
74
|
skills?: ReadonlyArray<CodexSkillMetadata>;
|
|
75
75
|
serviceAgentMode?: boolean;
|
|
76
76
|
}): CanonRuntimeDescriptor;
|
|
@@ -88,7 +88,7 @@ export declare function getCodexRequestingUserId(message: {
|
|
|
88
88
|
}): string | null;
|
|
89
89
|
export declare function resolveSessionExecutionMode(config: {
|
|
90
90
|
executionMode?: ExecutionEnvironmentMode;
|
|
91
|
-
} | null | undefined, serviceAgentMode?: boolean): ExecutionEnvironmentMode;
|
|
91
|
+
} | null | undefined, serviceAgentMode?: boolean, defaultExecutionMode?: ExecutionEnvironmentMode): ExecutionEnvironmentMode;
|
|
92
92
|
export declare function resolveCodexSessionConfig<T extends Record<string, unknown>>(config: T | null | undefined, serviceAgentMode?: boolean): T | null;
|
|
93
93
|
export declare function resolveWorkspaceCwd(config: {
|
|
94
94
|
workspaceId?: string;
|
package/dist/host.js
CHANGED
|
@@ -6,11 +6,11 @@ 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 { buildTrailBlockId, buildUndeliverableFinalNotice, captureTurnArtifactSnapshot, createTurnArtifactRouter, IDLE_TIMEOUT_MS, PLAN_BLOCK_TITLE, resolveTurnArtifactRouting, collectMissedInboundMessages, createRecoveryCheckpointTracker, createReconnectRecoveryCoordinator, STARTUP_RECOVERY_PAGE_SIZE, } from '@canonmsg/coding-agent-host';
|
|
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,
|
|
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, decideAutoReply, initRTDBAuth, buildLocalRuntimeId, heartbeatLocalRuntimeEntry, markLocalRuntimeStopped, normalizeTurnMetadata, parseTurnVerbosityConfig, RuntimeRequestManager, prepareConversationEnvironment, loadHostSessionConfig, releaseConversationEnvironment, resolveCanonAgent, verifyResolvedAgentEnvironment, CanonApiError, loadRuntimeSessionState, sendMessageWithRetry, sendMessageWithRetryChunked, saveRuntimeSessionState, publishHostAgentRuntime, publishHostSessionSnapshots, renderCanonHostInboundContent, renderCodingHostInboundPrompt, resolveHostWorkspaceCwd, isSilentTurnSuppressed, resolveSilentTurnDelivery, resolveTurnVerbosity, shouldTriggerAgentTurn, upsertLocalRuntimeEntry, } from '@canonmsg/core';
|
|
10
10
|
import { validateCard } from '@canonmsg/rich-cards';
|
|
11
11
|
import { CodexConversationAdapter, } from './adapter.js';
|
|
12
12
|
import { CodexAppServerAdapter, } from './app-server-adapter.js';
|
|
13
|
-
import { CODEX_APP_DYNAMIC_TOOLS, CODEX_NO_REPLY_MODEL_TOOL_NAME, CODEX_SERVICE_AGENT_DYNAMIC_TOOLS,
|
|
13
|
+
import { CODEX_APP_DYNAMIC_TOOLS, CODEX_NO_REPLY_MODEL_TOOL_NAME, CODEX_SERVICE_AGENT_DYNAMIC_TOOLS, answerCodexNoReply, classifyCodexAppToolRequest, deniedCodexAppToolResult, filterCodexCommunicationTools, handleCodexAppToolCall, handleCodexCommunicateToolCall, isCanonRuntimeControlToolCall, isCodexAppToolCall, isCodexCommunicateToolCall, isCodexServiceAgentToolCall, parseCanonRuntimeControlRequest, readCodexNoReplyReason, successfulCodexAppToolResult, } from './codex-app-tools.js';
|
|
14
14
|
import { mapCanonApprovalResultToCodexDecision, mapCodexAppServerApprovalRequest, } from './app-server-approval.js';
|
|
15
15
|
import { clearStoredThreadId, buildCodexThreadPolicyFingerprint, loadStoredThreadId, saveStoredThreadId, } from './session-store.js';
|
|
16
16
|
import { deriveCodexPermissionEnvelope, mapCanonPermissionToCodex, } from './permission-mode.js';
|
|
@@ -30,6 +30,7 @@ COMMON FLAGS
|
|
|
30
30
|
--cwd <path> Project directory to run Codex from
|
|
31
31
|
--workspace <path> Additional project to expose in Canon
|
|
32
32
|
--workspace-root <path> Discover projects under an approved root
|
|
33
|
+
--default-execution-mode <mode> New-conversation default: worktree or locked
|
|
33
34
|
--model <model> Default Codex model for new turns
|
|
34
35
|
--sandbox <mode> Codex sandbox mode
|
|
35
36
|
--full-auto Allow non-interactive write access
|
|
@@ -199,6 +200,7 @@ export function buildCodexRuntimeDescriptor(input) {
|
|
|
199
200
|
workspaces: serviceAgentMode ? [] : input.workspaces,
|
|
200
201
|
workspaceRoots: serviceAgentMode ? undefined : input.workspaceRoots,
|
|
201
202
|
executionModes: serviceAgentMode ? [] : input.executionModes,
|
|
203
|
+
defaultExecutionMode: serviceAgentMode ? 'locked' : input.defaultExecutionMode,
|
|
202
204
|
permissionModes: serviceAgentMode ? [] : input.permissionModes,
|
|
203
205
|
defaultPermissionMode: serviceAgentMode ? undefined : input.defaultPermissionMode,
|
|
204
206
|
permissionModeLabel: 'Execution policy',
|
|
@@ -210,9 +212,6 @@ export function buildCodexRuntimeDescriptor(input) {
|
|
|
210
212
|
effortLiveBehavior: 'next_turn',
|
|
211
213
|
presentation: input.presentation,
|
|
212
214
|
streamingTextMode: 'snapshot',
|
|
213
|
-
admissionActions: input.supportsCanonCommunicationTools && !serviceAgentMode
|
|
214
|
-
? { ...HOST_ADMISSION_ACTIONS_DISABLED, requestContact: true, reachOut: true }
|
|
215
|
-
: HOST_ADMISSION_ACTIONS_DISABLED,
|
|
216
215
|
...(input.supportsPlanMode && !serviceAgentMode
|
|
217
216
|
? {
|
|
218
217
|
turnModes: [
|
|
@@ -284,21 +283,25 @@ export function getCodexRequestingUserId(message) {
|
|
|
284
283
|
async function publishAgentRuntime(agentId, runtime, rtdb) {
|
|
285
284
|
await publishHostAgentRuntime(agentId, 'codex', runtime, rtdb);
|
|
286
285
|
}
|
|
287
|
-
async function loadSessionConfig(conversationId, agentId, rtdb
|
|
286
|
+
async function loadSessionConfig(conversationId, agentId, rtdb) {
|
|
288
287
|
return loadHostSessionConfig({
|
|
289
288
|
conversationId,
|
|
290
289
|
agentId,
|
|
291
290
|
rtdb,
|
|
292
291
|
extraStringFields: CODEX_SESSION_CONFIG_FIELDS,
|
|
293
|
-
retryMissingMs: retryMissing ? 3_000 : 0,
|
|
294
292
|
});
|
|
295
293
|
}
|
|
296
|
-
export function resolveSessionExecutionMode(config, serviceAgentMode = false) {
|
|
294
|
+
export function resolveSessionExecutionMode(config, serviceAgentMode = false, defaultExecutionMode = 'worktree') {
|
|
297
295
|
if (serviceAgentMode)
|
|
298
296
|
return 'locked';
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
297
|
+
return config?.executionMode ?? defaultExecutionMode;
|
|
298
|
+
}
|
|
299
|
+
function resolveConfiguredDefaultExecutionMode(value) {
|
|
300
|
+
if (value == null || value === '')
|
|
301
|
+
return 'worktree';
|
|
302
|
+
if (value === 'worktree' || value === 'locked')
|
|
303
|
+
return value;
|
|
304
|
+
throw new Error('--default-execution-mode must be worktree or locked');
|
|
302
305
|
}
|
|
303
306
|
export function resolveCodexSessionConfig(config, serviceAgentMode = false) {
|
|
304
307
|
return serviceAgentMode ? null : config ?? null;
|
|
@@ -377,8 +380,8 @@ function buildCanonPrompt(input) {
|
|
|
377
380
|
message: input.message,
|
|
378
381
|
})), input.noReplyToolName ? { noReplyToolName: input.noReplyToolName } : {});
|
|
379
382
|
}
|
|
380
|
-
function renderInboundContent(message, materialized
|
|
381
|
-
return renderCanonHostInboundContent(message, materialized
|
|
383
|
+
function renderInboundContent(message, materialized) {
|
|
384
|
+
return renderCanonHostInboundContent(message, materialized);
|
|
382
385
|
}
|
|
383
386
|
async function materializePromptReplyContext(input) {
|
|
384
387
|
if (!input.replyContext?.found || !input.replyContext.attachments?.length) {
|
|
@@ -395,16 +398,6 @@ async function materializePromptReplyContext(input) {
|
|
|
395
398
|
return { replyContext: input.replyContext, materialized: [] };
|
|
396
399
|
}
|
|
397
400
|
}
|
|
398
|
-
function ownerBoundReplyContactTarget(replyContext) {
|
|
399
|
-
const card = replyContext?.found ? replyContext.contactCard : undefined;
|
|
400
|
-
if (!card?.userId)
|
|
401
|
-
return undefined;
|
|
402
|
-
return {
|
|
403
|
-
targetUserId: card.userId,
|
|
404
|
-
...(card.canonContactId ? { canonContactId: card.canonContactId } : {}),
|
|
405
|
-
sourceCardMessageId: replyContext.messageId,
|
|
406
|
-
};
|
|
407
|
-
}
|
|
408
401
|
function summarizeCommand(command) {
|
|
409
402
|
const trimmed = command.trim();
|
|
410
403
|
if (!trimmed)
|
|
@@ -650,6 +643,7 @@ export async function main() {
|
|
|
650
643
|
'add-dir': { type: 'string', multiple: true },
|
|
651
644
|
workspace: { type: 'string', multiple: true },
|
|
652
645
|
'workspace-root': { type: 'string', multiple: true },
|
|
646
|
+
'default-execution-mode': { type: 'string' },
|
|
653
647
|
config: { type: 'string', multiple: true },
|
|
654
648
|
'codex-bin': { type: 'string' },
|
|
655
649
|
'runtime-visibility': { type: 'string' },
|
|
@@ -674,8 +668,11 @@ export async function main() {
|
|
|
674
668
|
onWarning: (message) => console.error(`[canon-codex] ${message}`),
|
|
675
669
|
});
|
|
676
670
|
const serviceAgentMode = args['service-agent'] === true;
|
|
671
|
+
const defaultExecutionMode = serviceAgentMode
|
|
672
|
+
? 'locked'
|
|
673
|
+
: resolveConfiguredDefaultExecutionMode(args['default-execution-mode']);
|
|
677
674
|
const nativeVisionEnabled = args['no-native-vision'] !== true;
|
|
678
|
-
const
|
|
675
|
+
const baseCodexDynamicTools = serviceAgentMode
|
|
679
676
|
? CODEX_SERVICE_AGENT_DYNAMIC_TOOLS
|
|
680
677
|
: CODEX_APP_DYNAMIC_TOOLS;
|
|
681
678
|
workingDir = (typeof args.cwd === 'string' ? args.cwd : null) || process.cwd();
|
|
@@ -720,11 +717,13 @@ export async function main() {
|
|
|
720
717
|
let agentId;
|
|
721
718
|
let ownerId = null;
|
|
722
719
|
let ownerName = null;
|
|
720
|
+
let outboundPolicy = null;
|
|
723
721
|
try {
|
|
724
722
|
const ctx = await client.getAgentMe();
|
|
725
723
|
agentId = ctx.agentId;
|
|
726
724
|
ownerId = ctx.ownerId;
|
|
727
725
|
ownerName = ctx.ownerName;
|
|
726
|
+
outboundPolicy = ctx.outboundPolicy;
|
|
728
727
|
console.error(`[canon-codex] Connected as ${ctx.displayName || agentId}`);
|
|
729
728
|
}
|
|
730
729
|
catch {
|
|
@@ -737,6 +736,7 @@ export async function main() {
|
|
|
737
736
|
}
|
|
738
737
|
console.error(`[canon-codex] Authenticated as ${agentId}`);
|
|
739
738
|
}
|
|
739
|
+
const codexDynamicTools = filterCodexCommunicationTools(baseCodexDynamicTools, outboundPolicy);
|
|
740
740
|
// Shared poll/timeout engine. Built-in `input`/`card` descriptors own
|
|
741
741
|
// create+poll (codex passes native/responder policy via payload/options).
|
|
742
742
|
// Approval keeps codex's own resolution shape (no owner-authored outcome
|
|
@@ -927,7 +927,6 @@ export async function main() {
|
|
|
927
927
|
ownerName,
|
|
928
928
|
membershipChange: pendingMembershipChanges.get(input.conversationId) ?? null,
|
|
929
929
|
groupContextMode: getGroupContextMode(input.conversationId, conversation),
|
|
930
|
-
renderOptions: input.renderOptions,
|
|
931
930
|
});
|
|
932
931
|
}
|
|
933
932
|
function writeState(session) {
|
|
@@ -1225,7 +1224,6 @@ export async function main() {
|
|
|
1225
1224
|
session.currentTurnOpenedAt = null;
|
|
1226
1225
|
session.currentTurnUpdatedAt = null;
|
|
1227
1226
|
session.currentTurnCanUseCodexAppTools = false;
|
|
1228
|
-
session.currentTurnCanUseCodexCanonTools = false;
|
|
1229
1227
|
session.currentTurnSilenced = false;
|
|
1230
1228
|
session.lastAcceptedIntent = null;
|
|
1231
1229
|
session.resetRequested = false;
|
|
@@ -1263,8 +1261,8 @@ export async function main() {
|
|
|
1263
1261
|
evictOldestIdle();
|
|
1264
1262
|
}
|
|
1265
1263
|
const creation = (async () => {
|
|
1266
|
-
const config = resolveCodexSessionConfig(await loadSessionConfig(conversationId, agentId, rtdb
|
|
1267
|
-
const sessionExecutionMode = resolveSessionExecutionMode(config, serviceAgentMode);
|
|
1264
|
+
const config = resolveCodexSessionConfig(await loadSessionConfig(conversationId, agentId, rtdb), serviceAgentMode);
|
|
1265
|
+
const sessionExecutionMode = resolveSessionExecutionMode(config, serviceAgentMode, defaultExecutionMode);
|
|
1268
1266
|
const workspaceCwd = resolveWorkspaceCwd(config, serviceAgentMode);
|
|
1269
1267
|
const environment = prepareConversationEnvironment({
|
|
1270
1268
|
agentId,
|
|
@@ -1338,8 +1336,6 @@ export async function main() {
|
|
|
1338
1336
|
currentTurnOpenedAt: null,
|
|
1339
1337
|
currentTurnUpdatedAt: null,
|
|
1340
1338
|
currentTurnCanUseCodexAppTools: false,
|
|
1341
|
-
currentTurnCanUseCodexCanonTools: false,
|
|
1342
|
-
currentTurnReplyContactTarget: null,
|
|
1343
1339
|
currentTurnAbortController: null,
|
|
1344
1340
|
// Corrected by the first turn that runs; a session with no turn
|
|
1345
1341
|
// publishes nothing anyway, and quiet is never an accident.
|
|
@@ -1386,10 +1382,8 @@ export async function main() {
|
|
|
1386
1382
|
planMode,
|
|
1387
1383
|
artifactRoutingMode: turn.artifactRoutingMode ?? 'disabled',
|
|
1388
1384
|
canUseCodexAppTools: turn.canUseCodexAppTools ?? false,
|
|
1389
|
-
canUseCodexCanonTools: turn.canUseCodexCanonTools ?? false,
|
|
1390
1385
|
...(turn.turnVerbosity ? { turnVerbosity: turn.turnVerbosity } : {}),
|
|
1391
1386
|
requestingUserId: turn.requestingUserId ?? null,
|
|
1392
|
-
...(turn.replyContactTarget ? { replyContactTarget: turn.replyContactTarget } : {}),
|
|
1393
1387
|
};
|
|
1394
1388
|
if (toFront) {
|
|
1395
1389
|
session.queue.unshift(nextPrompt);
|
|
@@ -1420,38 +1414,6 @@ export async function main() {
|
|
|
1420
1414
|
requestingUserId: responseUserId,
|
|
1421
1415
|
});
|
|
1422
1416
|
}
|
|
1423
|
-
function recordContactLifecycleEvent(request) {
|
|
1424
|
-
if (request.requesterId !== agentId || !request.sourceConversationId)
|
|
1425
|
-
return false;
|
|
1426
|
-
return recordLocalRuntimeContactLifecycleEvent(runtimeId, request);
|
|
1427
|
-
}
|
|
1428
|
-
async function reconcileContactLifecycleInbox() {
|
|
1429
|
-
let cursor = readLocalRuntimeEntry(runtimeId)?.contactLifecycleCursor ?? null;
|
|
1430
|
-
let recorded = 0;
|
|
1431
|
-
let duplicate = 0;
|
|
1432
|
-
let ignored = 0;
|
|
1433
|
-
for (let pageNumber = 0; pageNumber < 10; pageNumber += 1) {
|
|
1434
|
-
const page = await client.listContactRequestLifecyclePage({
|
|
1435
|
-
cursor,
|
|
1436
|
-
limit: 100,
|
|
1437
|
-
});
|
|
1438
|
-
const result = await reconcileContactLifecycleEvents({
|
|
1439
|
-
requests: page.requests,
|
|
1440
|
-
requesterId: agentId,
|
|
1441
|
-
record: (request) => recordLocalRuntimeContactLifecycleEvent(runtimeId, request),
|
|
1442
|
-
});
|
|
1443
|
-
recorded += result.recorded;
|
|
1444
|
-
duplicate += result.duplicate;
|
|
1445
|
-
ignored += result.ignored;
|
|
1446
|
-
cursor = page.nextCursor;
|
|
1447
|
-
saveLocalRuntimeContactLifecycleCursor(runtimeId, cursor);
|
|
1448
|
-
if (!page.hasMore)
|
|
1449
|
-
break;
|
|
1450
|
-
}
|
|
1451
|
-
if (recorded > 0) {
|
|
1452
|
-
console.error(`[canon-codex] Contact lifecycle recovery: recorded=${recorded} duplicate=${duplicate} ignored=${ignored}`);
|
|
1453
|
-
}
|
|
1454
|
-
}
|
|
1455
1417
|
function resolveArtifactRoutingMode(participantContext) {
|
|
1456
1418
|
return participantContext.conversationType === 'direct' && participantContext.isOwner
|
|
1457
1419
|
? 'workspace-generated'
|
|
@@ -1463,19 +1425,15 @@ export async function main() {
|
|
|
1463
1425
|
* so a prompt that waits behind another still runs under the answer it
|
|
1464
1426
|
* arrived with.
|
|
1465
1427
|
*/
|
|
1466
|
-
function resolveCodexTurnModes(participantContext, message
|
|
1428
|
+
function resolveCodexTurnModes(participantContext, message) {
|
|
1467
1429
|
return {
|
|
1468
1430
|
artifactRoutingMode: resolveArtifactRoutingMode(participantContext),
|
|
1469
1431
|
canUseCodexAppTools: participantContext.isOwner || serviceAgentMode,
|
|
1470
|
-
canUseCodexCanonTools: participantContext.isOwner && !serviceAgentMode,
|
|
1471
1432
|
turnVerbosity: resolveTurnVerbosity({
|
|
1472
1433
|
configured: configuredTurnVerbosity,
|
|
1473
1434
|
conversationType: participantContext.conversationType,
|
|
1474
1435
|
}),
|
|
1475
1436
|
requestingUserId: getCodexRequestingUserId(message),
|
|
1476
|
-
...(participantContext.isOwner && replyContext
|
|
1477
|
-
? { replyContactTarget: ownerBoundReplyContactTarget(replyContext) }
|
|
1478
|
-
: {}),
|
|
1479
1437
|
};
|
|
1480
1438
|
}
|
|
1481
1439
|
function runtimeCardRequestPayload(method, params) {
|
|
@@ -1492,6 +1450,12 @@ export async function main() {
|
|
|
1492
1450
|
const params = request.params;
|
|
1493
1451
|
const expiresAt = Date.now() + 30 * 60_000;
|
|
1494
1452
|
if (request.method === 'item/tool/call' && isCodexAppToolCall(params)) {
|
|
1453
|
+
if (isCodexCommunicateToolCall(params)) {
|
|
1454
|
+
if (!(session.adapter instanceof CodexAppServerAdapter)) {
|
|
1455
|
+
return deniedCodexAppToolResult('This Codex transport does not support dynamic tools.');
|
|
1456
|
+
}
|
|
1457
|
+
return handleCodexCommunicateToolCall(client, params);
|
|
1458
|
+
}
|
|
1495
1459
|
if (serviceAgentMode && !isCodexServiceAgentToolCall(params)) {
|
|
1496
1460
|
return deniedCodexAppToolResult('This service agent exposes only Canon conversation tools.');
|
|
1497
1461
|
}
|
|
@@ -1517,23 +1481,6 @@ export async function main() {
|
|
|
1517
1481
|
if (disposition === 'denied-non-owner') {
|
|
1518
1482
|
return deniedCodexAppToolResult('Only the Canon owner can use codex_app tools.');
|
|
1519
1483
|
}
|
|
1520
|
-
if (isCodexCanonVerbToolCall(params)) {
|
|
1521
|
-
if (!session.currentTurnCanUseCodexCanonTools) {
|
|
1522
|
-
return deniedCodexAppToolResult('Canon contact and conversation tools require a real owner-authored inbound turn.');
|
|
1523
|
-
}
|
|
1524
|
-
return await answerCodexCanonVerb({
|
|
1525
|
-
client,
|
|
1526
|
-
params,
|
|
1527
|
-
context: {
|
|
1528
|
-
conversationId: session.conversationId,
|
|
1529
|
-
sourceMessageId,
|
|
1530
|
-
turnId: session.currentTurnId,
|
|
1531
|
-
...(session.currentTurnReplyContactTarget
|
|
1532
|
-
? { replyContactTarget: session.currentTurnReplyContactTarget }
|
|
1533
|
-
: {}),
|
|
1534
|
-
},
|
|
1535
|
-
});
|
|
1536
|
-
}
|
|
1537
1484
|
if (isCanonRuntimeControlToolCall(params)) {
|
|
1538
1485
|
const command = parseCanonRuntimeControlRequest(params);
|
|
1539
1486
|
if (!command) {
|
|
@@ -1873,8 +1820,7 @@ export async function main() {
|
|
|
1873
1820
|
console.error(`[canon-codex] [${input.conversationId.slice(0, 8)}] Failed to materialize media:`, error instanceof Error ? error.message : error);
|
|
1874
1821
|
}
|
|
1875
1822
|
}
|
|
1876
|
-
const
|
|
1877
|
-
const renderedContent = renderInboundContent(input.message, materialized, renderOptions);
|
|
1823
|
+
const renderedContent = renderInboundContent(input.message, materialized);
|
|
1878
1824
|
const turnMetadata = normalizeTurnMetadata(input.message.metadata);
|
|
1879
1825
|
const requestedPlanMode = turnMetadata?.requestedTurnMode === 'plan';
|
|
1880
1826
|
const planCommand = resolveCodexPlanCommand({
|
|
@@ -1893,7 +1839,6 @@ export async function main() {
|
|
|
1893
1839
|
selfContexts: input.selfContexts,
|
|
1894
1840
|
provenance: input.provenance,
|
|
1895
1841
|
hydratedPage: input.hydratedPage,
|
|
1896
|
-
renderOptions,
|
|
1897
1842
|
});
|
|
1898
1843
|
const behavior = input.behavior ?? hydrated.behavior;
|
|
1899
1844
|
const activeSelfContextId = hydrated.activeSelfContextId;
|
|
@@ -1948,7 +1893,7 @@ export async function main() {
|
|
|
1948
1893
|
return;
|
|
1949
1894
|
}
|
|
1950
1895
|
session.activeSelfContextId = activeSelfContextId;
|
|
1951
|
-
const
|
|
1896
|
+
const prompt = buildCanonPrompt({
|
|
1952
1897
|
content,
|
|
1953
1898
|
conversationId: input.conversationId,
|
|
1954
1899
|
participantContext,
|
|
@@ -1960,12 +1905,8 @@ export async function main() {
|
|
|
1960
1905
|
message: input.message,
|
|
1961
1906
|
...(useAppServer ? { noReplyToolName: CODEX_NO_REPLY_MODEL_TOOL_NAME } : {}),
|
|
1962
1907
|
});
|
|
1963
|
-
const lifecycleContext = input.isOwner
|
|
1964
|
-
? formatPendingContactLifecycleContext(takeLocalRuntimeContactLifecycleEvents(runtimeId, input.conversationId))
|
|
1965
|
-
: null;
|
|
1966
|
-
const prompt = lifecycleContext ? `${basePrompt}\n\n${lifecycleContext}` : basePrompt;
|
|
1967
1908
|
if (session.running && deliveryIntent === 'interrupt') {
|
|
1968
|
-
enqueuePrompt(session, prompt, deliveryIntent, true, input.message.id, shouldMarkAccepted, imagePaths, mediaAddDirs, planCommand.planMode, resolveCodexTurnModes(participantContext, input.message
|
|
1909
|
+
enqueuePrompt(session, prompt, deliveryIntent, true, input.message.id, shouldMarkAccepted, imagePaths, mediaAddDirs, planCommand.planMode, resolveCodexTurnModes(participantContext, input.message));
|
|
1969
1910
|
console.error(`[canon-codex] [${input.conversationId.slice(0, 8)}] Interrupting current turn for explicit human send-now`);
|
|
1970
1911
|
session.currentTurnAbortController?.abort(new Error('Codex turn interrupted by a newer message'));
|
|
1971
1912
|
await session.adapter.interrupt().catch(() => { });
|
|
@@ -1973,7 +1914,7 @@ export async function main() {
|
|
|
1973
1914
|
typingSignals.clear(input.conversationId).catch(() => { });
|
|
1974
1915
|
return;
|
|
1975
1916
|
}
|
|
1976
|
-
enqueuePrompt(session, prompt, deliveryIntent, false, input.message.id, shouldMarkAccepted, imagePaths, mediaAddDirs, planCommand.planMode, resolveCodexTurnModes(participantContext, input.message
|
|
1917
|
+
enqueuePrompt(session, prompt, deliveryIntent, false, input.message.id, shouldMarkAccepted, imagePaths, mediaAddDirs, planCommand.planMode, resolveCodexTurnModes(participantContext, input.message));
|
|
1977
1918
|
}
|
|
1978
1919
|
function sendTurnArtifactFile(session, file) {
|
|
1979
1920
|
return sendMediaFileMessage(client, session.conversationId, file.path, '', {
|
|
@@ -2004,8 +1945,6 @@ export async function main() {
|
|
|
2004
1945
|
session.currentTurnOpenedAt = Date.now();
|
|
2005
1946
|
session.currentTurnUpdatedAt = session.currentTurnOpenedAt;
|
|
2006
1947
|
session.currentTurnCanUseCodexAppTools = nextTurn.canUseCodexAppTools === true;
|
|
2007
|
-
session.currentTurnCanUseCodexCanonTools = nextTurn.canUseCodexCanonTools === true;
|
|
2008
|
-
session.currentTurnReplyContactTarget = nextTurn.replyContactTarget ?? null;
|
|
2009
1948
|
session.currentTurnAbortController = new AbortController();
|
|
2010
1949
|
// A continuation prompt (a plan-review result) carries none, and keeps the
|
|
2011
1950
|
// conversation's last answer rather than silently reverting to verbose.
|
|
@@ -2419,8 +2358,6 @@ export async function main() {
|
|
|
2419
2358
|
session.currentTurnOpenedAt = null;
|
|
2420
2359
|
session.currentTurnUpdatedAt = null;
|
|
2421
2360
|
session.currentTurnCanUseCodexAppTools = false;
|
|
2422
|
-
session.currentTurnCanUseCodexCanonTools = false;
|
|
2423
|
-
session.currentTurnReplyContactTarget = null;
|
|
2424
2361
|
session.currentTurnSilenced = false;
|
|
2425
2362
|
session.lastAcceptedIntent = null;
|
|
2426
2363
|
session.resetRequested = false;
|
|
@@ -2479,6 +2416,7 @@ export async function main() {
|
|
|
2479
2416
|
});
|
|
2480
2417
|
let codexSkills = [];
|
|
2481
2418
|
const buildCurrentRuntimeDescriptor = () => ({
|
|
2419
|
+
defaultExecutionMode,
|
|
2482
2420
|
...(serviceAgentMode
|
|
2483
2421
|
? {}
|
|
2484
2422
|
: {
|
|
@@ -2498,31 +2436,18 @@ export async function main() {
|
|
|
2498
2436
|
workspaces: buildPublicWorkspaceOptions(workspaceOptions),
|
|
2499
2437
|
workspaceRoots: workspaceRootMetadata,
|
|
2500
2438
|
executionModes: hostAvailableExecutionModes,
|
|
2439
|
+
defaultExecutionMode,
|
|
2501
2440
|
permissionModes: [...codexPermissionEnvelope.availablePermissionModes],
|
|
2502
2441
|
defaultPermissionMode: codexPermissionEnvelope.defaultPermissionMode,
|
|
2503
2442
|
presentation: runtimePresentation,
|
|
2504
2443
|
supportsPlanMode: useAppServer,
|
|
2505
2444
|
supportsCompact: useAppServer,
|
|
2506
2445
|
supportsRichCards: useAppServer,
|
|
2507
|
-
supportsCanonCommunicationTools: useAppServer,
|
|
2508
2446
|
skills: codexSkills,
|
|
2509
2447
|
serviceAgentMode,
|
|
2510
2448
|
}),
|
|
2511
2449
|
});
|
|
2512
2450
|
let runtimeDescriptor = buildCurrentRuntimeDescriptor();
|
|
2513
|
-
const resolveInboundContentRenderOptions = (isOwnerTurn) => {
|
|
2514
|
-
const descriptorActions = runtimeDescriptor.runtimeDescriptor?.admissionActions
|
|
2515
|
-
?? HOST_ADMISSION_ACTIONS_DISABLED;
|
|
2516
|
-
const admissionActions = isOwnerTurn && useAppServer && !serviceAgentMode
|
|
2517
|
-
? descriptorActions
|
|
2518
|
-
: HOST_ADMISSION_ACTIONS_DISABLED;
|
|
2519
|
-
return {
|
|
2520
|
-
admissionActions,
|
|
2521
|
-
...(admissionActions.reachOut
|
|
2522
|
-
? { reachOutToolName: 'codex_app.canon_send_to' }
|
|
2523
|
-
: {}),
|
|
2524
|
-
};
|
|
2525
|
-
};
|
|
2526
2451
|
async function refreshCodexSkillInventory(forceReload = false) {
|
|
2527
2452
|
if (!useAppServer)
|
|
2528
2453
|
return;
|
|
@@ -2844,12 +2769,6 @@ export async function main() {
|
|
|
2844
2769
|
const conversationsDiscoveredWhileOffline = startupRecoveryComplete
|
|
2845
2770
|
? new Set([...knownConversationIds].filter((id) => !knownBeforeRefresh.has(id)))
|
|
2846
2771
|
: new Set();
|
|
2847
|
-
try {
|
|
2848
|
-
await reconcileContactLifecycleInbox();
|
|
2849
|
-
}
|
|
2850
|
-
catch (error) {
|
|
2851
|
-
console.error('[canon-codex] Contact lifecycle recovery failed:', error instanceof Error ? error.message : error);
|
|
2852
|
-
}
|
|
2853
2772
|
for (const conversationId of knownConversationIds) {
|
|
2854
2773
|
const recoveryCheckpoints = recoveryCheckpointsFor(conversationId);
|
|
2855
2774
|
const recoveryBatch = recoveryCheckpoints.reserveBatch();
|
|
@@ -2957,9 +2876,6 @@ export async function main() {
|
|
|
2957
2876
|
onConversationUpdated: (payload) => {
|
|
2958
2877
|
handleConversationUpdated(payload);
|
|
2959
2878
|
},
|
|
2960
|
-
onContactRequestUpdated: (payload) => {
|
|
2961
|
-
recordContactLifecycleEvent(payload);
|
|
2962
|
-
},
|
|
2963
2879
|
onConnected: () => {
|
|
2964
2880
|
streamConnected = true;
|
|
2965
2881
|
void publishRuntimeHeartbeat();
|
package/dist/register.js
CHANGED
|
@@ -28,7 +28,6 @@ After approval, start it with CANON_AGENT=<profile> canon-codex --cwd /path/to/p
|
|
|
28
28
|
const OPTIONS = {
|
|
29
29
|
moduleUrl: import.meta.url,
|
|
30
30
|
clientType: 'codex',
|
|
31
|
-
sessionSetupPolicy: 'runtime_descriptor_required',
|
|
32
31
|
cliName: 'canon-codex-register',
|
|
33
32
|
hostBinName: 'canon-codex',
|
|
34
33
|
developerInfo: 'Codex host plugin',
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@canonmsg/codex-plugin",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.28.0",
|
|
4
4
|
"description": "Canon host integration for Codex CLI",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/index.js",
|
|
@@ -29,11 +29,11 @@
|
|
|
29
29
|
"prepack": "npm run build"
|
|
30
30
|
},
|
|
31
31
|
"dependencies": {
|
|
32
|
-
"@canonmsg/agent-sdk": "^
|
|
33
|
-
"@canonmsg/agent-tools": "^0.
|
|
34
|
-
"@canonmsg/coding-agent-host": "^0.
|
|
35
|
-
"@canonmsg/core": "^
|
|
36
|
-
"@canonmsg/rich-cards": "^0.10.
|
|
32
|
+
"@canonmsg/agent-sdk": "^9.0.0",
|
|
33
|
+
"@canonmsg/agent-tools": "^0.7.0",
|
|
34
|
+
"@canonmsg/coding-agent-host": "^0.7.0",
|
|
35
|
+
"@canonmsg/core": "^11.0.0",
|
|
36
|
+
"@canonmsg/rich-cards": "^0.10.3"
|
|
37
37
|
},
|
|
38
38
|
"engines": {
|
|
39
39
|
"node": ">=18.0.0"
|