@canonmsg/codex-plugin 0.26.2 → 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/dist/codex-app-tools.d.ts +6 -1
- package/dist/codex-app-tools.js +29 -2
- package/dist/host.d.ts +4 -2
- package/dist/host.js +148 -100
- package/package.json +7 -6
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { type NoReplyReportClient } from '@canonmsg/core';
|
|
1
|
+
import { type CanonClient, type NoReplyReportClient } from '@canonmsg/core';
|
|
2
2
|
type JsonRecord = Record<string, unknown>;
|
|
3
3
|
interface DynamicToolSpec {
|
|
4
4
|
[key: string]: unknown;
|
|
@@ -44,6 +44,7 @@ type DynamicToolCallResponse = {
|
|
|
44
44
|
*/
|
|
45
45
|
export declare const CODEX_NO_REPLY_TOOL_NAME = "no_reply";
|
|
46
46
|
export declare const CANON_RUNTIME_CONTROL_TOOL_NAME = "canon_runtime_control";
|
|
47
|
+
export declare const CODEX_COMMUNICATE_TOOL_NAME = "communicate";
|
|
47
48
|
/**
|
|
48
49
|
* What the model actually sees for the tool above, for prompt text that names
|
|
49
50
|
* it (the group posture cue). Only meaningful on the app-server transport —
|
|
@@ -53,6 +54,8 @@ export declare const CODEX_NO_REPLY_MODEL_TOOL_NAME = "codex_app.no_reply";
|
|
|
53
54
|
export declare const CODEX_APP_DYNAMIC_TOOLS: ReadonlyArray<DynamicToolSpec>;
|
|
54
55
|
/** The complete model-visible surface for non-coding service agents. */
|
|
55
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>;
|
|
56
59
|
/**
|
|
57
60
|
* Tools exposed to detached Codex threads created through the bridge. Canon
|
|
58
61
|
* conversation controls belong only to the foreground turn that received the
|
|
@@ -65,7 +68,9 @@ export declare function successfulCodexAppToolResult(payload: unknown): DynamicT
|
|
|
65
68
|
/** True when this dynamic-tool call is the deliberate-silence verb. */
|
|
66
69
|
export declare function isCodexNoReplyToolCall(params: CodexAppToolCallParams): boolean;
|
|
67
70
|
export declare function isCanonRuntimeControlToolCall(params: CodexAppToolCallParams): boolean;
|
|
71
|
+
export declare function isCodexCommunicateToolCall(params: CodexAppToolCallParams): boolean;
|
|
68
72
|
export declare function isCodexServiceAgentToolCall(params: CodexAppToolCallParams): boolean;
|
|
73
|
+
export declare function handleCodexCommunicateToolCall(client: CanonClient, params: CodexAppToolCallParams): Promise<DynamicToolCallResponse>;
|
|
69
74
|
export interface CanonRuntimeControlRequest {
|
|
70
75
|
action: 'send_card' | 'request_card' | 'request_input';
|
|
71
76
|
card?: unknown;
|
package/dist/codex-app-tools.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import { NO_REPLY_ACK_NOTE, reportNoReplyOutcome } from '@canonmsg/core';
|
|
1
|
+
import { NO_REPLY_ACK_NOTE, reportNoReplyOutcome, } from '@canonmsg/core';
|
|
2
|
+
import { canonCommunicateToolDefinition, createCanonCommunicationBinding, } from '@canonmsg/agent-tools';
|
|
2
3
|
const emptyObjectSchema = {
|
|
3
4
|
type: 'object',
|
|
4
5
|
properties: {},
|
|
@@ -90,6 +91,7 @@ function tool(name, description, inputSchema, deferLoading = true) {
|
|
|
90
91
|
*/
|
|
91
92
|
export const CODEX_NO_REPLY_TOOL_NAME = 'no_reply';
|
|
92
93
|
export const CANON_RUNTIME_CONTROL_TOOL_NAME = 'canon_runtime_control';
|
|
94
|
+
export const CODEX_COMMUNICATE_TOOL_NAME = 'communicate';
|
|
93
95
|
/**
|
|
94
96
|
* What the model actually sees for the tool above, for prompt text that names
|
|
95
97
|
* it (the group posture cue). Only meaningful on the app-server transport —
|
|
@@ -121,6 +123,11 @@ const CANON_RUNTIME_CONTROL_TOOL = tool(CANON_RUNTIME_CONTROL_TOOL_NAME, 'Intera
|
|
|
121
123
|
},
|
|
122
124
|
required: ['action'],
|
|
123
125
|
}, false);
|
|
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);
|
|
124
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 '
|
|
125
132
|
+ 'chats when you have nothing to add — no message is created, so no other '
|
|
126
133
|
+ 'member or agent is triggered. Optional private reason (logged, never '
|
|
@@ -245,6 +252,7 @@ export const CODEX_APP_DYNAMIC_TOOLS = [
|
|
|
245
252
|
required: ['threadId', 'title'],
|
|
246
253
|
}),
|
|
247
254
|
CANON_RUNTIME_CONTROL_TOOL,
|
|
255
|
+
CODEX_COMMUNICATE_TOOL,
|
|
248
256
|
// Canon's `no_reply` verb, projected into the only model-visible tool surface
|
|
249
257
|
// the Codex transport gives us. It is answered by the Canon host itself.
|
|
250
258
|
CODEX_NO_REPLY_TOOL,
|
|
@@ -252,8 +260,15 @@ export const CODEX_APP_DYNAMIC_TOOLS = [
|
|
|
252
260
|
/** The complete model-visible surface for non-coding service agents. */
|
|
253
261
|
export const CODEX_SERVICE_AGENT_DYNAMIC_TOOLS = [
|
|
254
262
|
CANON_RUNTIME_CONTROL_TOOL,
|
|
263
|
+
CODEX_COMMUNICATE_TOOL,
|
|
255
264
|
CODEX_NO_REPLY_TOOL,
|
|
256
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
|
+
}
|
|
257
272
|
/**
|
|
258
273
|
* Tools exposed to detached Codex threads created through the bridge. Canon
|
|
259
274
|
* conversation controls belong only to the foreground turn that received the
|
|
@@ -294,9 +309,21 @@ export function isCodexNoReplyToolCall(params) {
|
|
|
294
309
|
export function isCanonRuntimeControlToolCall(params) {
|
|
295
310
|
return normalizeToolName(params.tool) === CANON_RUNTIME_CONTROL_TOOL_NAME;
|
|
296
311
|
}
|
|
312
|
+
export function isCodexCommunicateToolCall(params) {
|
|
313
|
+
return normalizeToolName(params.tool) === CODEX_COMMUNICATE_TOOL_NAME;
|
|
314
|
+
}
|
|
297
315
|
export function isCodexServiceAgentToolCall(params) {
|
|
298
316
|
const toolName = normalizeToolName(params.tool);
|
|
299
|
-
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;
|
|
320
|
+
}
|
|
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
|
+
};
|
|
300
327
|
}
|
|
301
328
|
export function parseCanonRuntimeControlRequest(params) {
|
|
302
329
|
if (!isCanonRuntimeControlToolCall(params))
|
package/dist/host.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import { type TurnArtifactRoutingDecision, type TurnArtifactRoutingMode } from '@canonmsg/coding-agent-host';
|
|
2
|
+
import { type TurnArtifactRoutingDecision, type TurnArtifactRoutingMode, type RecoveryCheckpointTracker } from '@canonmsg/coding-agent-host';
|
|
3
3
|
import { type CanonRuntimeCommandDescriptor, type CanonRuntimeDescriptor, type CanonRuntimePresentationPolicy, type ExecutionEnvironmentMode, type PreparedExecutionEnvironment, type WorkspaceOption, type CanonWorkspaceRootMetadata, type RuntimeStreamingPayload, type TurnLifecycleState, type TurnOutputBlock, type TurnVerbosity, type TurnVerbosityConfig } from '@canonmsg/core';
|
|
4
4
|
import { type CodexSandboxMode } from './adapter.js';
|
|
5
5
|
import { type CodexSkillMetadata } from './app-server-adapter.js';
|
|
@@ -49,6 +49,7 @@ export declare function buildCodexServiceSnapshotConfig(input: {
|
|
|
49
49
|
executionMode: "locked";
|
|
50
50
|
executionBranch: null;
|
|
51
51
|
};
|
|
52
|
+
export declare function createCodexRecoveryCheckpointTracker(persist: (messageId: string) => boolean): RecoveryCheckpointTracker;
|
|
52
53
|
/** Conservative fallback used only when native app-server discovery is unavailable. */
|
|
53
54
|
export declare const CODEX_EFFORT_OPTIONS: readonly CodexControlOption[];
|
|
54
55
|
export declare const CODEX_SESSION_CONFIG_FIELDS: readonly ["permissionMode", "effort"];
|
|
@@ -60,6 +61,7 @@ export declare function buildCodexRuntimeDescriptor(input: {
|
|
|
60
61
|
workspaces: WorkspaceOption[];
|
|
61
62
|
workspaceRoots?: CanonWorkspaceRootMetadata[];
|
|
62
63
|
executionModes: ExecutionEnvironmentMode[];
|
|
64
|
+
defaultExecutionMode?: ExecutionEnvironmentMode;
|
|
63
65
|
permissionModes: Array<{
|
|
64
66
|
value: string;
|
|
65
67
|
label: string;
|
|
@@ -86,7 +88,7 @@ export declare function getCodexRequestingUserId(message: {
|
|
|
86
88
|
}): string | null;
|
|
87
89
|
export declare function resolveSessionExecutionMode(config: {
|
|
88
90
|
executionMode?: ExecutionEnvironmentMode;
|
|
89
|
-
} | null | undefined, serviceAgentMode?: boolean): ExecutionEnvironmentMode;
|
|
91
|
+
} | null | undefined, serviceAgentMode?: boolean, defaultExecutionMode?: ExecutionEnvironmentMode): ExecutionEnvironmentMode;
|
|
90
92
|
export declare function resolveCodexSessionConfig<T extends Record<string, unknown>>(config: T | null | undefined, serviceAgentMode?: boolean): T | null;
|
|
91
93
|
export declare function resolveWorkspaceCwd(config: {
|
|
92
94
|
workspaceId?: string;
|
package/dist/host.js
CHANGED
|
@@ -5,12 +5,12 @@ 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, sendMediaFileMessage, } from '@canonmsg/agent-sdk';
|
|
8
|
-
import { buildTrailBlockId, buildUndeliverableFinalNotice, captureTurnArtifactSnapshot, createTurnArtifactRouter, IDLE_TIMEOUT_MS, PLAN_BLOCK_TITLE, resolveTurnArtifactRouting, collectMissedInboundMessages,
|
|
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
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, answerCodexNoReply, classifyCodexAppToolRequest, deniedCodexAppToolResult, handleCodexAppToolCall, isCanonRuntimeControlToolCall, isCodexAppToolCall, isCodexServiceAgentToolCall, parseCanonRuntimeControlRequest, readCodexNoReplyReason, successfulCodexAppToolResult, } from './codex-app-tools.js';
|
|
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
|
|
@@ -93,6 +94,9 @@ export function buildCodexServiceSnapshotConfig(input) {
|
|
|
93
94
|
};
|
|
94
95
|
}
|
|
95
96
|
const MAX_SESSIONS = 12;
|
|
97
|
+
export function createCodexRecoveryCheckpointTracker(persist) {
|
|
98
|
+
return createRecoveryCheckpointTracker(persist);
|
|
99
|
+
}
|
|
96
100
|
// IDLE_TIMEOUT_MS (30 minutes) is shared with the other coding-agent hosts.
|
|
97
101
|
const HEARTBEAT_MS = 30_000;
|
|
98
102
|
/** How Codex says it finished the work but Canon would not take the answer. */
|
|
@@ -196,6 +200,7 @@ export function buildCodexRuntimeDescriptor(input) {
|
|
|
196
200
|
workspaces: serviceAgentMode ? [] : input.workspaces,
|
|
197
201
|
workspaceRoots: serviceAgentMode ? undefined : input.workspaceRoots,
|
|
198
202
|
executionModes: serviceAgentMode ? [] : input.executionModes,
|
|
203
|
+
defaultExecutionMode: serviceAgentMode ? 'locked' : input.defaultExecutionMode,
|
|
199
204
|
permissionModes: serviceAgentMode ? [] : input.permissionModes,
|
|
200
205
|
defaultPermissionMode: serviceAgentMode ? undefined : input.defaultPermissionMode,
|
|
201
206
|
permissionModeLabel: 'Execution policy',
|
|
@@ -286,12 +291,17 @@ async function loadSessionConfig(conversationId, agentId, rtdb) {
|
|
|
286
291
|
extraStringFields: CODEX_SESSION_CONFIG_FIELDS,
|
|
287
292
|
});
|
|
288
293
|
}
|
|
289
|
-
export function resolveSessionExecutionMode(config, serviceAgentMode = false) {
|
|
294
|
+
export function resolveSessionExecutionMode(config, serviceAgentMode = false, defaultExecutionMode = 'worktree') {
|
|
290
295
|
if (serviceAgentMode)
|
|
291
296
|
return 'locked';
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
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');
|
|
295
305
|
}
|
|
296
306
|
export function resolveCodexSessionConfig(config, serviceAgentMode = false) {
|
|
297
307
|
return serviceAgentMode ? null : config ?? null;
|
|
@@ -633,6 +643,7 @@ export async function main() {
|
|
|
633
643
|
'add-dir': { type: 'string', multiple: true },
|
|
634
644
|
workspace: { type: 'string', multiple: true },
|
|
635
645
|
'workspace-root': { type: 'string', multiple: true },
|
|
646
|
+
'default-execution-mode': { type: 'string' },
|
|
636
647
|
config: { type: 'string', multiple: true },
|
|
637
648
|
'codex-bin': { type: 'string' },
|
|
638
649
|
'runtime-visibility': { type: 'string' },
|
|
@@ -657,8 +668,11 @@ export async function main() {
|
|
|
657
668
|
onWarning: (message) => console.error(`[canon-codex] ${message}`),
|
|
658
669
|
});
|
|
659
670
|
const serviceAgentMode = args['service-agent'] === true;
|
|
671
|
+
const defaultExecutionMode = serviceAgentMode
|
|
672
|
+
? 'locked'
|
|
673
|
+
: resolveConfiguredDefaultExecutionMode(args['default-execution-mode']);
|
|
660
674
|
const nativeVisionEnabled = args['no-native-vision'] !== true;
|
|
661
|
-
const
|
|
675
|
+
const baseCodexDynamicTools = serviceAgentMode
|
|
662
676
|
? CODEX_SERVICE_AGENT_DYNAMIC_TOOLS
|
|
663
677
|
: CODEX_APP_DYNAMIC_TOOLS;
|
|
664
678
|
workingDir = (typeof args.cwd === 'string' ? args.cwd : null) || process.cwd();
|
|
@@ -703,11 +717,13 @@ export async function main() {
|
|
|
703
717
|
let agentId;
|
|
704
718
|
let ownerId = null;
|
|
705
719
|
let ownerName = null;
|
|
720
|
+
let outboundPolicy = null;
|
|
706
721
|
try {
|
|
707
722
|
const ctx = await client.getAgentMe();
|
|
708
723
|
agentId = ctx.agentId;
|
|
709
724
|
ownerId = ctx.ownerId;
|
|
710
725
|
ownerName = ctx.ownerName;
|
|
726
|
+
outboundPolicy = ctx.outboundPolicy;
|
|
711
727
|
console.error(`[canon-codex] Connected as ${ctx.displayName || agentId}`);
|
|
712
728
|
}
|
|
713
729
|
catch {
|
|
@@ -720,6 +736,7 @@ export async function main() {
|
|
|
720
736
|
}
|
|
721
737
|
console.error(`[canon-codex] Authenticated as ${agentId}`);
|
|
722
738
|
}
|
|
739
|
+
const codexDynamicTools = filterCodexCommunicationTools(baseCodexDynamicTools, outboundPolicy);
|
|
723
740
|
// Shared poll/timeout engine. Built-in `input`/`card` descriptors own
|
|
724
741
|
// create+poll (codex passes native/responder policy via payload/options).
|
|
725
742
|
// Approval keeps codex's own resolution shape (no owner-authored outcome
|
|
@@ -816,7 +833,15 @@ export async function main() {
|
|
|
816
833
|
});
|
|
817
834
|
const sessions = new Map();
|
|
818
835
|
const pendingSessionCreations = new Map();
|
|
819
|
-
|
|
836
|
+
const recoveryCheckpointTrackers = new Map();
|
|
837
|
+
function recoveryCheckpointsFor(conversationId) {
|
|
838
|
+
let tracker = recoveryCheckpointTrackers.get(conversationId);
|
|
839
|
+
if (!tracker) {
|
|
840
|
+
tracker = createCodexRecoveryCheckpointTracker((messageId) => persistInboundRecoveryCursor(conversationId, messageId));
|
|
841
|
+
recoveryCheckpointTrackers.set(conversationId, tracker);
|
|
842
|
+
}
|
|
843
|
+
return tracker;
|
|
844
|
+
}
|
|
820
845
|
const conversationCache = new Map();
|
|
821
846
|
const knownConversationIds = new Set();
|
|
822
847
|
const promptedGroupContextConversationIds = new Set();
|
|
@@ -964,27 +989,20 @@ export async function main() {
|
|
|
964
989
|
}
|
|
965
990
|
function persistInboundRecoveryCursor(conversationId, messageId) {
|
|
966
991
|
if (!messageId)
|
|
967
|
-
return;
|
|
992
|
+
return true;
|
|
968
993
|
try {
|
|
969
994
|
saveRuntimeSessionState(runtimeId, {
|
|
970
995
|
conversationId,
|
|
971
996
|
baseCwd: workingDir,
|
|
972
997
|
lastInboundMessageId: messageId,
|
|
973
998
|
});
|
|
999
|
+
return true;
|
|
974
1000
|
}
|
|
975
1001
|
catch (error) {
|
|
976
1002
|
console.error(`[canon-codex] [${conversationId.slice(0, 8)}] Failed to persist inbound recovery cursor:`, error instanceof Error ? error.message : error);
|
|
1003
|
+
return false;
|
|
977
1004
|
}
|
|
978
1005
|
}
|
|
979
|
-
function persistInboundRecoveryCursorWhenIdle(conversationId, messageId) {
|
|
980
|
-
const session = sessions.get(conversationId);
|
|
981
|
-
// A later queued turn will advance past this non-triggering message. Do
|
|
982
|
-
// not write it ahead of earlier work and then let that work regress the
|
|
983
|
-
// cursor when it completes.
|
|
984
|
-
if (session?.running || (session?.queue.length ?? 0) > 0)
|
|
985
|
-
return;
|
|
986
|
-
persistInboundRecoveryCursor(conversationId, messageId);
|
|
987
|
-
}
|
|
988
1006
|
async function markQueuedPromptsRejected(conversationId, prompts) {
|
|
989
1007
|
await Promise.all(prompts.map((prompt) => {
|
|
990
1008
|
if (!prompt.markAccepted || !prompt.sourceMessageId)
|
|
@@ -992,27 +1010,21 @@ export async function main() {
|
|
|
992
1010
|
return client.updateMessageDisposition(conversationId, prompt.sourceMessageId, 'rejected').catch(() => { });
|
|
993
1011
|
}));
|
|
994
1012
|
}
|
|
995
|
-
function
|
|
996
|
-
const
|
|
997
|
-
|
|
998
|
-
|
|
999
|
-
if (!latest)
|
|
1000
|
-
return;
|
|
1001
|
-
if (session.running) {
|
|
1002
|
-
session.pendingDroppedRecoveryCursor = latest;
|
|
1003
|
-
return;
|
|
1004
|
-
}
|
|
1005
|
-
persistInboundRecoveryCursor(session.conversationId, latest);
|
|
1013
|
+
function settleRejectedPromptCheckpoints(conversationId, prompts) {
|
|
1014
|
+
const checkpoints = recoveryCheckpointsFor(conversationId);
|
|
1015
|
+
for (const prompt of prompts)
|
|
1016
|
+
checkpoints.settle(prompt.sourceMessageId);
|
|
1006
1017
|
}
|
|
1007
1018
|
function removeQueuedPrompt(conversationId, sourceMessageId) {
|
|
1008
1019
|
const session = sessions.get(conversationId);
|
|
1009
1020
|
if (!session || session.queue.length === 0)
|
|
1010
1021
|
return;
|
|
1011
|
-
const
|
|
1022
|
+
const removed = session.queue.filter((prompt) => prompt.sourceMessageId === sourceMessageId);
|
|
1023
|
+
if (removed.length === 0)
|
|
1024
|
+
return;
|
|
1012
1025
|
session.queue = session.queue.filter((prompt) => prompt.sourceMessageId !== sourceMessageId);
|
|
1013
|
-
|
|
1014
|
-
|
|
1015
|
-
}
|
|
1026
|
+
settleRejectedPromptCheckpoints(conversationId, removed);
|
|
1027
|
+
writeTurn(session);
|
|
1016
1028
|
}
|
|
1017
1029
|
function clearStreaming(conversationId) {
|
|
1018
1030
|
runtimeState.clearStreaming(conversationId).catch(() => { });
|
|
@@ -1196,7 +1208,7 @@ export async function main() {
|
|
|
1196
1208
|
session.resetRequested = true;
|
|
1197
1209
|
const droppedPrompts = session.queue.splice(0);
|
|
1198
1210
|
await markQueuedPromptsRejected(conversationId, droppedPrompts);
|
|
1199
|
-
|
|
1211
|
+
settleRejectedPromptCheckpoints(conversationId, droppedPrompts);
|
|
1200
1212
|
clearStoredThreadId(runtimeId, conversationId, session.environment.baseCwd, session.environment.mode);
|
|
1201
1213
|
session.adapter.clearThreadId();
|
|
1202
1214
|
session.activeSelfContextId = null;
|
|
@@ -1250,7 +1262,7 @@ export async function main() {
|
|
|
1250
1262
|
}
|
|
1251
1263
|
const creation = (async () => {
|
|
1252
1264
|
const config = resolveCodexSessionConfig(await loadSessionConfig(conversationId, agentId, rtdb), serviceAgentMode);
|
|
1253
|
-
const sessionExecutionMode = resolveSessionExecutionMode(config, serviceAgentMode);
|
|
1265
|
+
const sessionExecutionMode = resolveSessionExecutionMode(config, serviceAgentMode, defaultExecutionMode);
|
|
1254
1266
|
const workspaceCwd = resolveWorkspaceCwd(config, serviceAgentMode);
|
|
1255
1267
|
const environment = prepareConversationEnvironment({
|
|
1256
1268
|
agentId,
|
|
@@ -1331,7 +1343,6 @@ export async function main() {
|
|
|
1331
1343
|
currentTurnSilenced: false,
|
|
1332
1344
|
activeSelfContextId: null,
|
|
1333
1345
|
lastAcceptedIntent: null,
|
|
1334
|
-
pendingDroppedRecoveryCursor: null,
|
|
1335
1346
|
resetRequested: false,
|
|
1336
1347
|
lastActivity: Date.now(),
|
|
1337
1348
|
typingKeepaliveTimer: null,
|
|
@@ -1373,7 +1384,6 @@ export async function main() {
|
|
|
1373
1384
|
canUseCodexAppTools: turn.canUseCodexAppTools ?? false,
|
|
1374
1385
|
...(turn.turnVerbosity ? { turnVerbosity: turn.turnVerbosity } : {}),
|
|
1375
1386
|
requestingUserId: turn.requestingUserId ?? null,
|
|
1376
|
-
recoverySequence: ++inboundRecoverySequence,
|
|
1377
1387
|
};
|
|
1378
1388
|
if (toFront) {
|
|
1379
1389
|
session.queue.unshift(nextPrompt);
|
|
@@ -1440,6 +1450,12 @@ export async function main() {
|
|
|
1440
1450
|
const params = request.params;
|
|
1441
1451
|
const expiresAt = Date.now() + 30 * 60_000;
|
|
1442
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
|
+
}
|
|
1443
1459
|
if (serviceAgentMode && !isCodexServiceAgentToolCall(params)) {
|
|
1444
1460
|
return deniedCodexAppToolResult('This service agent exposes only Canon conversation tools.');
|
|
1445
1461
|
}
|
|
@@ -1759,7 +1775,7 @@ export async function main() {
|
|
|
1759
1775
|
knownConversationIds.add(input.conversationId);
|
|
1760
1776
|
if (input.turnDispatch && input.turnDispatch.kind !== 'run_turn') {
|
|
1761
1777
|
console.error(`[canon-codex] [${input.conversationId.slice(0, 8)}] Suppressed server-dispatched turn: ${input.turnDispatch.reason}`);
|
|
1762
|
-
|
|
1778
|
+
recoveryCheckpointsFor(input.conversationId).settle(input.message.id);
|
|
1763
1779
|
return;
|
|
1764
1780
|
}
|
|
1765
1781
|
if (input.message.metadata?.type === 'plan_approval_reply') {
|
|
@@ -1767,7 +1783,7 @@ export async function main() {
|
|
|
1767
1783
|
// A service agent never advertises or enters plan mode. Consume stale
|
|
1768
1784
|
// replies left by an older coding descriptor instead of turning them
|
|
1769
1785
|
// into hidden plan/implementation prompts after an upgrade or restart.
|
|
1770
|
-
|
|
1786
|
+
recoveryCheckpointsFor(input.conversationId).settle(input.message.id);
|
|
1771
1787
|
return;
|
|
1772
1788
|
}
|
|
1773
1789
|
const planId = readString(input.message.metadata, 'planId');
|
|
@@ -1775,7 +1791,7 @@ export async function main() {
|
|
|
1775
1791
|
senderId: input.message.senderId,
|
|
1776
1792
|
metadata: input.message.metadata,
|
|
1777
1793
|
})) {
|
|
1778
|
-
|
|
1794
|
+
recoveryCheckpointsFor(input.conversationId).settle(input.message.id);
|
|
1779
1795
|
return;
|
|
1780
1796
|
}
|
|
1781
1797
|
const session = await getOrCreateSession(input.conversationId);
|
|
@@ -1849,7 +1865,7 @@ export async function main() {
|
|
|
1849
1865
|
: decideAutoReply(participantContext, behavior);
|
|
1850
1866
|
if (!autoReply.allow) {
|
|
1851
1867
|
console.error(`[canon-codex] [${input.conversationId.slice(0, 8)}] Suppressed auto-reply: ${autoReply.reason}`);
|
|
1852
|
-
|
|
1868
|
+
recoveryCheckpointsFor(input.conversationId).settle(input.message.id);
|
|
1853
1869
|
return;
|
|
1854
1870
|
}
|
|
1855
1871
|
console.error(`[canon-codex] [${input.conversationId.slice(0, 8)}] Message from ${input.senderName}: "${content.slice(0, 80)}" (${autoReply.reason})`);
|
|
@@ -1873,7 +1889,7 @@ export async function main() {
|
|
|
1873
1889
|
replyBehavior: 'suppress_auto_reply',
|
|
1874
1890
|
},
|
|
1875
1891
|
}).catch(() => { });
|
|
1876
|
-
|
|
1892
|
+
recoveryCheckpointsFor(input.conversationId).settle(input.message.id);
|
|
1877
1893
|
return;
|
|
1878
1894
|
}
|
|
1879
1895
|
session.activeSelfContextId = activeSelfContextId;
|
|
@@ -2333,11 +2349,7 @@ export async function main() {
|
|
|
2333
2349
|
finally {
|
|
2334
2350
|
session.currentTurnAbortController?.abort(new Error('Codex turn ended'));
|
|
2335
2351
|
session.currentTurnAbortController = null;
|
|
2336
|
-
|
|
2337
|
-
if (session.pendingDroppedRecoveryCursor) {
|
|
2338
|
-
persistInboundRecoveryCursor(session.conversationId, session.pendingDroppedRecoveryCursor);
|
|
2339
|
-
session.pendingDroppedRecoveryCursor = null;
|
|
2340
|
-
}
|
|
2352
|
+
recoveryCheckpointsFor(session.conversationId).settle(nextTurn.sourceMessageId);
|
|
2341
2353
|
stopVisibleWorkSignal(session);
|
|
2342
2354
|
session.running = false;
|
|
2343
2355
|
session.state.state = 'idle';
|
|
@@ -2404,6 +2416,7 @@ export async function main() {
|
|
|
2404
2416
|
});
|
|
2405
2417
|
let codexSkills = [];
|
|
2406
2418
|
const buildCurrentRuntimeDescriptor = () => ({
|
|
2419
|
+
defaultExecutionMode,
|
|
2407
2420
|
...(serviceAgentMode
|
|
2408
2421
|
? {}
|
|
2409
2422
|
: {
|
|
@@ -2423,6 +2436,7 @@ export async function main() {
|
|
|
2423
2436
|
workspaces: buildPublicWorkspaceOptions(workspaceOptions),
|
|
2424
2437
|
workspaceRoots: workspaceRootMetadata,
|
|
2425
2438
|
executionModes: hostAvailableExecutionModes,
|
|
2439
|
+
defaultExecutionMode,
|
|
2426
2440
|
permissionModes: [...codexPermissionEnvelope.availablePermissionModes],
|
|
2427
2441
|
defaultPermissionMode: codexPermissionEnvelope.defaultPermissionMode,
|
|
2428
2442
|
presentation: runtimePresentation,
|
|
@@ -2550,7 +2564,7 @@ export async function main() {
|
|
|
2550
2564
|
if (type === 'stop_and_drop') {
|
|
2551
2565
|
const droppedPrompts = session.queue.splice(0);
|
|
2552
2566
|
await markQueuedPromptsRejected(conversationId, droppedPrompts);
|
|
2553
|
-
|
|
2567
|
+
settleRejectedPromptCheckpoints(conversationId, droppedPrompts);
|
|
2554
2568
|
}
|
|
2555
2569
|
if (session.running) {
|
|
2556
2570
|
session.currentTurnAbortController?.abort(new Error(`Codex turn interrupted by ${type}`));
|
|
@@ -2745,6 +2759,84 @@ export async function main() {
|
|
|
2745
2759
|
publishRuntimeDetailsInFlight = false;
|
|
2746
2760
|
}
|
|
2747
2761
|
};
|
|
2762
|
+
let startupRecoveryComplete = false;
|
|
2763
|
+
async function recoverInboundMessageGaps() {
|
|
2764
|
+
// A reconnect can reveal conversations created while this host was
|
|
2765
|
+
// offline. Refresh before sweeping cursors; this is one REST reconciliation
|
|
2766
|
+
// pass, not an automatic retry loop.
|
|
2767
|
+
const knownBeforeRefresh = new Set(knownConversationIds);
|
|
2768
|
+
await refreshKnownConversationIds(true);
|
|
2769
|
+
const conversationsDiscoveredWhileOffline = startupRecoveryComplete
|
|
2770
|
+
? new Set([...knownConversationIds].filter((id) => !knownBeforeRefresh.has(id)))
|
|
2771
|
+
: new Set();
|
|
2772
|
+
for (const conversationId of knownConversationIds) {
|
|
2773
|
+
const recoveryCheckpoints = recoveryCheckpointsFor(conversationId);
|
|
2774
|
+
const recoveryBatch = recoveryCheckpoints.reserveBatch();
|
|
2775
|
+
try {
|
|
2776
|
+
const cursor = loadRuntimeSessionState(runtimeId, {
|
|
2777
|
+
conversationId,
|
|
2778
|
+
baseCwd: workingDir,
|
|
2779
|
+
})?.lastInboundMessageId ?? null;
|
|
2780
|
+
const recovered = await collectMissedInboundMessages({
|
|
2781
|
+
fetchPage: (before) => client.getMessagesPage(conversationId, STARTUP_RECOVERY_PAGE_SIZE, before),
|
|
2782
|
+
cursor,
|
|
2783
|
+
agentId,
|
|
2784
|
+
requireContiguousCursor: true,
|
|
2785
|
+
noCursorMode: conversationsDiscoveredWhileOffline.has(conversationId)
|
|
2786
|
+
? 'bounded-window'
|
|
2787
|
+
: 'latest-only',
|
|
2788
|
+
});
|
|
2789
|
+
recoveryBatch.commit(recovered.messages.map((message) => message.id), recovered.mode === 'incomplete-gap' ? recovered.recoveryCursor : null);
|
|
2790
|
+
if (recovered.mode === 'incomplete-gap') {
|
|
2791
|
+
console.error(`[canon-codex] [${conversationId.slice(0, 8)}] recovery_incomplete_gap: cursor is missing; replaying ${recovered.messages.length} bounded inbound message(s) before advancing to ${recovered.recoveryCursor ?? 'no cursor'}`);
|
|
2792
|
+
}
|
|
2793
|
+
for (const message of recovered.messages) {
|
|
2794
|
+
const isPlanReply = isCodexPlanApprovalReply(message.metadata, serviceAgentMode);
|
|
2795
|
+
if (!isPlanReply && !shouldTriggerAgentTurn({
|
|
2796
|
+
senderType: message.senderType,
|
|
2797
|
+
metadata: message.metadata,
|
|
2798
|
+
}).allow) {
|
|
2799
|
+
recoveryCheckpoints.settle(message.id);
|
|
2800
|
+
continue;
|
|
2801
|
+
}
|
|
2802
|
+
if (!claimInboundMessageId(message.id))
|
|
2803
|
+
continue;
|
|
2804
|
+
try {
|
|
2805
|
+
await enqueueInboundMessage({
|
|
2806
|
+
conversationId,
|
|
2807
|
+
message,
|
|
2808
|
+
senderName: message.senderName || message.senderId,
|
|
2809
|
+
isOwner: message.senderId === ownerId,
|
|
2810
|
+
behavior: recovered.newestPage.behavior,
|
|
2811
|
+
activeSelfContextId: recovered.newestPage.activeSelfContextIdByMessageId?.[message.id] ?? null,
|
|
2812
|
+
selfContexts: recovered.newestPage.selfContexts,
|
|
2813
|
+
hydratedPage: recovered.newestPage,
|
|
2814
|
+
});
|
|
2815
|
+
settleInboundMessageId(message.id, true);
|
|
2816
|
+
}
|
|
2817
|
+
catch (error) {
|
|
2818
|
+
settleInboundMessageId(message.id, false);
|
|
2819
|
+
throw error;
|
|
2820
|
+
}
|
|
2821
|
+
}
|
|
2822
|
+
if (recovered.messages.length > 0) {
|
|
2823
|
+
console.error(`[canon-codex] [${conversationId.slice(0, 8)}] Recovered ${recovered.messages.length} inbound message(s) (${recovered.mode})`);
|
|
2824
|
+
}
|
|
2825
|
+
}
|
|
2826
|
+
catch (error) {
|
|
2827
|
+
recoveryBatch.cancel();
|
|
2828
|
+
console.error(`[canon-codex] [${conversationId.slice(0, 8)}] Recovery failed:`, error instanceof Error ? error.message : error);
|
|
2829
|
+
}
|
|
2830
|
+
}
|
|
2831
|
+
}
|
|
2832
|
+
const reconnectRecovery = createReconnectRecoveryCoordinator(recoverInboundMessageGaps);
|
|
2833
|
+
const observeReconnectRecovery = (reason, recovery) => {
|
|
2834
|
+
if (!recovery)
|
|
2835
|
+
return;
|
|
2836
|
+
void recovery.catch((error) => {
|
|
2837
|
+
console.error(`[canon-codex] ${reason} recovery failed:`, error instanceof Error ? error.message : error);
|
|
2838
|
+
});
|
|
2839
|
+
};
|
|
2748
2840
|
const stream = new CanonStream({
|
|
2749
2841
|
apiKey,
|
|
2750
2842
|
agentId,
|
|
@@ -2756,9 +2848,10 @@ export async function main() {
|
|
|
2756
2848
|
return;
|
|
2757
2849
|
if (!claimInboundMessageId(message.id))
|
|
2758
2850
|
return;
|
|
2851
|
+
recoveryCheckpointsFor(payload.conversationId).track(message.id);
|
|
2759
2852
|
if (payload.turnDispatch && payload.turnDispatch.kind !== 'run_turn') {
|
|
2760
2853
|
console.error(`[canon-codex] [${payload.conversationId.slice(0, 8)}] Ignoring server-dispatched observe-only message: ${payload.turnDispatch.reason}`);
|
|
2761
|
-
|
|
2854
|
+
recoveryCheckpointsFor(payload.conversationId).settle(message.id);
|
|
2762
2855
|
settleInboundMessageId(message.id, true);
|
|
2763
2856
|
return;
|
|
2764
2857
|
}
|
|
@@ -2786,6 +2879,7 @@ export async function main() {
|
|
|
2786
2879
|
onConnected: () => {
|
|
2787
2880
|
streamConnected = true;
|
|
2788
2881
|
void publishRuntimeHeartbeat();
|
|
2882
|
+
observeReconnectRecovery('reconnect', reconnectRecovery.onConnected());
|
|
2789
2883
|
console.error('[canon-codex] SSE connected');
|
|
2790
2884
|
},
|
|
2791
2885
|
onDisconnected: () => {
|
|
@@ -2793,6 +2887,7 @@ export async function main() {
|
|
|
2793
2887
|
runtimeState.clearAgentRuntime().catch(() => { });
|
|
2794
2888
|
console.error('[canon-codex] SSE disconnected');
|
|
2795
2889
|
},
|
|
2890
|
+
onReplayExpired: () => observeReconnectRecovery('replay-expired', reconnectRecovery.onReplayExpired()),
|
|
2796
2891
|
onError: (error) => console.error(`[canon-codex] SSE error: ${error.message}`),
|
|
2797
2892
|
},
|
|
2798
2893
|
});
|
|
@@ -2811,57 +2906,10 @@ export async function main() {
|
|
|
2811
2906
|
catch (error) {
|
|
2812
2907
|
console.error('[canon-codex] Failed to load startup conversations:', error);
|
|
2813
2908
|
}
|
|
2814
|
-
|
|
2815
|
-
|
|
2816
|
-
|
|
2817
|
-
|
|
2818
|
-
baseCwd: workingDir,
|
|
2819
|
-
})?.lastInboundMessageId ?? null;
|
|
2820
|
-
const recovered = await collectMissedInboundMessages({
|
|
2821
|
-
fetchPage: (before) => client.getMessagesPage(conversationId, STARTUP_RECOVERY_PAGE_SIZE, before),
|
|
2822
|
-
cursor,
|
|
2823
|
-
agentId,
|
|
2824
|
-
});
|
|
2825
|
-
if (recovered.mode === 'truncated-window') {
|
|
2826
|
-
console.error(`[canon-codex] [${conversationId.slice(0, 8)}] Recovery cursor was not found within ${STARTUP_RECOVERY_MAX_MESSAGES} messages; replaying the bounded recent window`);
|
|
2827
|
-
}
|
|
2828
|
-
for (const message of recovered.messages) {
|
|
2829
|
-
const isPlanReply = isCodexPlanApprovalReply(message.metadata, serviceAgentMode);
|
|
2830
|
-
if (!isPlanReply && !shouldTriggerAgentTurn({
|
|
2831
|
-
senderType: message.senderType,
|
|
2832
|
-
metadata: message.metadata,
|
|
2833
|
-
}).allow) {
|
|
2834
|
-
persistInboundRecoveryCursorWhenIdle(conversationId, message.id);
|
|
2835
|
-
continue;
|
|
2836
|
-
}
|
|
2837
|
-
if (!claimInboundMessageId(message.id))
|
|
2838
|
-
continue;
|
|
2839
|
-
try {
|
|
2840
|
-
await enqueueInboundMessage({
|
|
2841
|
-
conversationId,
|
|
2842
|
-
message,
|
|
2843
|
-
senderName: message.senderName || message.senderId,
|
|
2844
|
-
isOwner: message.senderId === ownerId,
|
|
2845
|
-
behavior: recovered.newestPage.behavior,
|
|
2846
|
-
activeSelfContextId: recovered.newestPage.activeSelfContextIdByMessageId?.[message.id] ?? null,
|
|
2847
|
-
selfContexts: recovered.newestPage.selfContexts,
|
|
2848
|
-
hydratedPage: recovered.newestPage,
|
|
2849
|
-
});
|
|
2850
|
-
settleInboundMessageId(message.id, true);
|
|
2851
|
-
}
|
|
2852
|
-
catch (error) {
|
|
2853
|
-
settleInboundMessageId(message.id, false);
|
|
2854
|
-
throw error;
|
|
2855
|
-
}
|
|
2856
|
-
}
|
|
2857
|
-
if (recovered.messages.length > 0) {
|
|
2858
|
-
console.error(`[canon-codex] [${conversationId.slice(0, 8)}] Recovered ${recovered.messages.length} inbound message(s) (${recovered.mode})`);
|
|
2859
|
-
}
|
|
2860
|
-
}
|
|
2861
|
-
catch (error) {
|
|
2862
|
-
console.error(`[canon-codex] [${conversationId.slice(0, 8)}] Startup recovery failed:`, error instanceof Error ? error.message : error);
|
|
2863
|
-
}
|
|
2864
|
-
}
|
|
2909
|
+
await reconnectRecovery.recoverNow().catch((error) => {
|
|
2910
|
+
console.error('[canon-codex] Startup recovery failed:', error instanceof Error ? error.message : error);
|
|
2911
|
+
});
|
|
2912
|
+
startupRecoveryComplete = true;
|
|
2865
2913
|
startCodexStreamInBackground(stream, (error) => {
|
|
2866
2914
|
console.error('[canon-codex] SSE start error:', error instanceof Error ? error.message : error);
|
|
2867
2915
|
});
|
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",
|
|
@@ -21,7 +21,7 @@
|
|
|
21
21
|
"scripts"
|
|
22
22
|
],
|
|
23
23
|
"scripts": {
|
|
24
|
-
"prepare:workspace-deps": "node ../../scripts/run-workspace-prep.mjs ../core ../agent-sdk ../coding-agent-host ../rich-cards",
|
|
24
|
+
"prepare:workspace-deps": "node ../../scripts/run-workspace-prep.mjs ../core ../agent-sdk ../agent-tools ../coding-agent-host ../rich-cards",
|
|
25
25
|
"build": "npm run prepare:workspace-deps && node -e \"require('fs').rmSync('dist',{recursive:true,force:true})\" && tsc",
|
|
26
26
|
"dev": "npm run prepare:workspace-deps && tsc --watch",
|
|
27
27
|
"smoke": "node scripts/smoke-test.mjs",
|
|
@@ -29,10 +29,11 @@
|
|
|
29
29
|
"prepack": "npm run build"
|
|
30
30
|
},
|
|
31
31
|
"dependencies": {
|
|
32
|
-
"@canonmsg/agent-sdk": "^
|
|
33
|
-
"@canonmsg/
|
|
34
|
-
"@canonmsg/
|
|
35
|
-
"@canonmsg/
|
|
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"
|
|
36
37
|
},
|
|
37
38
|
"engines": {
|
|
38
39
|
"node": ">=18.0.0"
|