@canonmsg/codex-plugin 0.28.0 → 0.29.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 +2 -0
- package/dist/codex-app-tools.js +6 -4
- package/dist/control-channel.d.ts +4 -14
- package/dist/control-channel.js +3 -10
- package/dist/host.d.ts +2 -37
- package/dist/host.js +96 -177
- package/dist/register.js +2 -0
- package/dist/session-store.d.ts +5 -1
- package/dist/session-store.js +14 -2
- package/package.json +5 -5
|
@@ -21,6 +21,8 @@ export interface CodexAppToolRuntime {
|
|
|
21
21
|
currentThreadId: string | null;
|
|
22
22
|
currentCwd: string;
|
|
23
23
|
workspaces: ReadonlyArray<CodexAppToolWorkspace>;
|
|
24
|
+
/** Whether detached work may see Canon's autonomous communication surface. */
|
|
25
|
+
communicationEnabled?: boolean;
|
|
24
26
|
model?: string | null;
|
|
25
27
|
effort?: string | null;
|
|
26
28
|
}
|
package/dist/codex-app-tools.js
CHANGED
|
@@ -265,9 +265,11 @@ export const CODEX_SERVICE_AGENT_DYNAMIC_TOOLS = [
|
|
|
265
265
|
];
|
|
266
266
|
/** Closed agents are not shown the optional outbound communication surface. */
|
|
267
267
|
export function filterCodexCommunicationTools(tools, outboundPolicy) {
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
268
|
+
const communicationEnabled = outboundPolicy === 'open'
|
|
269
|
+
|| outboundPolicy === 'approval-required';
|
|
270
|
+
return communicationEnabled
|
|
271
|
+
? tools
|
|
272
|
+
: tools.filter((entry) => entry.name !== CODEX_COMMUNICATE_TOOL_NAME);
|
|
271
273
|
}
|
|
272
274
|
/**
|
|
273
275
|
* Tools exposed to detached Codex threads created through the bridge. Canon
|
|
@@ -465,7 +467,7 @@ async function createThread(runtime, args) {
|
|
|
465
467
|
const started = await runtime.adapter.requestAppServer('thread/start', {
|
|
466
468
|
cwd,
|
|
467
469
|
...(readString(args, 'model') ?? runtime.model ? { model: readString(args, 'model') ?? runtime.model } : {}),
|
|
468
|
-
dynamicTools: CODEX_DETACHED_THREAD_DYNAMIC_TOOLS,
|
|
470
|
+
dynamicTools: filterCodexCommunicationTools(CODEX_DETACHED_THREAD_DYNAMIC_TOOLS, runtime.communicationEnabled === true ? 'open' : null),
|
|
469
471
|
experimentalRawEvents: false,
|
|
470
472
|
persistExtendedHistory: true,
|
|
471
473
|
});
|
|
@@ -5,18 +5,14 @@
|
|
|
5
5
|
* codex host characterization profile (the "codex host profile" describe
|
|
6
6
|
* block in core's control-poller.test.ts is the contract):
|
|
7
7
|
*
|
|
8
|
-
* - Keys: `
|
|
9
|
-
*
|
|
10
|
-
* at a time.
|
|
8
|
+
* - Keys: `signal`, plus `primitive` when a primitive handler is configured;
|
|
9
|
+
* read sequentially per conversation, conversations polled one at a time.
|
|
11
10
|
* - Cadence: immediate first cycle, then active/idle delays + jitter with
|
|
12
11
|
* the activity probe sampled BEFORE the cycle runs.
|
|
13
|
-
* - Session controls are always consumed once newer (default consume), even
|
|
14
|
-
* when no live session applied them; signals are NOT consumed when the
|
|
15
|
-
* handler throws (consumeOnError stays false).
|
|
16
12
|
* - Dedupe is primed eagerly via `poller.baseline([conversationId])` at
|
|
17
|
-
* session creation
|
|
13
|
+
* session creation.
|
|
18
14
|
*/
|
|
19
|
-
import { ControlChannelPoller, type ControlChannelRTDB, type ControlHandlerResult, type ControlPollerError, type ControlPrimitiveEvent, type
|
|
15
|
+
import { ControlChannelPoller, type ControlChannelRTDB, type ControlHandlerResult, type ControlPollerError, type ControlPrimitiveEvent, type ControlSignalEvent } from '@canonmsg/core';
|
|
20
16
|
export declare const CONTROL_POLL_MS = 2000;
|
|
21
17
|
export declare const IDLE_CONTROL_POLL_MS = 10000;
|
|
22
18
|
export declare const CONTROL_POLL_JITTER_MS = 1000;
|
|
@@ -28,12 +24,6 @@ export interface CodexControlChannelInput {
|
|
|
28
24
|
conversationIds: () => Iterable<string>;
|
|
29
25
|
/** Sampled before each cycle to choose the active vs idle delay. */
|
|
30
26
|
hasActiveWork: () => boolean;
|
|
31
|
-
/**
|
|
32
|
-
* Applies a newer `/session` control node. The node is always consumed
|
|
33
|
-
* after the handler resolves; a thrown error leaves it in place (with
|
|
34
|
-
* dedupe already advanced, so it is never retried).
|
|
35
|
-
*/
|
|
36
|
-
onSessionControl: (event: ControlSessionEvent) => Promise<ControlHandlerResult> | ControlHandlerResult;
|
|
37
27
|
/**
|
|
38
28
|
* Handles a newer `/signal` node. Return `{ consume: false }` to leave the
|
|
39
29
|
* node in place; thrown errors also leave it (consumeOnError stays false).
|
package/dist/control-channel.js
CHANGED
|
@@ -5,16 +5,12 @@
|
|
|
5
5
|
* codex host characterization profile (the "codex host profile" describe
|
|
6
6
|
* block in core's control-poller.test.ts is the contract):
|
|
7
7
|
*
|
|
8
|
-
* - Keys: `
|
|
9
|
-
*
|
|
10
|
-
* at a time.
|
|
8
|
+
* - Keys: `signal`, plus `primitive` when a primitive handler is configured;
|
|
9
|
+
* read sequentially per conversation, conversations polled one at a time.
|
|
11
10
|
* - Cadence: immediate first cycle, then active/idle delays + jitter with
|
|
12
11
|
* the activity probe sampled BEFORE the cycle runs.
|
|
13
|
-
* - Session controls are always consumed once newer (default consume), even
|
|
14
|
-
* when no live session applied them; signals are NOT consumed when the
|
|
15
|
-
* handler throws (consumeOnError stays false).
|
|
16
12
|
* - Dedupe is primed eagerly via `poller.baseline([conversationId])` at
|
|
17
|
-
* session creation
|
|
13
|
+
* session creation.
|
|
18
14
|
*/
|
|
19
15
|
import { ControlChannelPoller, } from '@canonmsg/core';
|
|
20
16
|
export const CONTROL_POLL_MS = 2_000;
|
|
@@ -36,9 +32,6 @@ export function createCodexControlPoller(input) {
|
|
|
36
32
|
pollOnStart: true,
|
|
37
33
|
conversationConcurrency: 'sequential',
|
|
38
34
|
handlers: {
|
|
39
|
-
session: {
|
|
40
|
-
handle: input.onSessionControl,
|
|
41
|
-
},
|
|
42
35
|
signal: {
|
|
43
36
|
handle: input.onSignal,
|
|
44
37
|
},
|
package/dist/host.d.ts
CHANGED
|
@@ -21,38 +21,9 @@ export declare function buildCodexInitialSessionState(input: {
|
|
|
21
21
|
permissionMode?: string;
|
|
22
22
|
effort?: string | null;
|
|
23
23
|
}): HostSessionState;
|
|
24
|
-
export declare function buildCodexLiveSessionConfig(input: {
|
|
25
|
-
model?: string;
|
|
26
|
-
permissionMode?: string;
|
|
27
|
-
effort?: string;
|
|
28
|
-
workspaceId?: string | null;
|
|
29
|
-
executionMode: ExecutionEnvironmentMode;
|
|
30
|
-
executionBranch?: string | null;
|
|
31
|
-
}): {
|
|
32
|
-
executionMode: ExecutionEnvironmentMode;
|
|
33
|
-
executionBranch: string | null;
|
|
34
|
-
workspaceId?: string | undefined;
|
|
35
|
-
effort?: string | undefined;
|
|
36
|
-
permissionMode?: string | undefined;
|
|
37
|
-
model?: string | undefined;
|
|
38
|
-
};
|
|
39
|
-
export declare function buildCodexServiceSnapshotConfig(input: {
|
|
40
|
-
model?: string;
|
|
41
|
-
permissionMode?: string;
|
|
42
|
-
effort?: string;
|
|
43
|
-
workspaceId?: string;
|
|
44
|
-
}): {
|
|
45
|
-
model: string | undefined;
|
|
46
|
-
permissionMode: string | undefined;
|
|
47
|
-
effort: string | undefined;
|
|
48
|
-
workspaceId: string | undefined;
|
|
49
|
-
executionMode: "locked";
|
|
50
|
-
executionBranch: null;
|
|
51
|
-
};
|
|
52
24
|
export declare function createCodexRecoveryCheckpointTracker(persist: (messageId: string) => boolean): RecoveryCheckpointTracker;
|
|
53
25
|
/** Conservative fallback used only when native app-server discovery is unavailable. */
|
|
54
26
|
export declare const CODEX_EFFORT_OPTIONS: readonly CodexControlOption[];
|
|
55
|
-
export declare const CODEX_SESSION_CONFIG_FIELDS: readonly ["permissionMode", "effort"];
|
|
56
27
|
export declare function buildCodexSkillCommands(skills: ReadonlyArray<CodexSkillMetadata>): CanonRuntimeCommandDescriptor[];
|
|
57
28
|
export declare function buildCodexRuntimeDescriptor(input: {
|
|
58
29
|
models: CodexControlOption[];
|
|
@@ -86,14 +57,8 @@ export declare function getCodexRequestingUserId(message: {
|
|
|
86
57
|
senderId: string;
|
|
87
58
|
senderType?: 'human' | 'ai_agent';
|
|
88
59
|
}): string | null;
|
|
89
|
-
export declare function resolveSessionExecutionMode(
|
|
90
|
-
|
|
91
|
-
} | null | undefined, serviceAgentMode?: boolean, defaultExecutionMode?: ExecutionEnvironmentMode): ExecutionEnvironmentMode;
|
|
92
|
-
export declare function resolveCodexSessionConfig<T extends Record<string, unknown>>(config: T | null | undefined, serviceAgentMode?: boolean): T | null;
|
|
93
|
-
export declare function resolveWorkspaceCwd(config: {
|
|
94
|
-
workspaceId?: string;
|
|
95
|
-
retiredWorkspaceConfig?: boolean;
|
|
96
|
-
} | null, serviceAgentMode?: boolean): string;
|
|
60
|
+
export declare function resolveSessionExecutionMode(serviceAgentMode?: boolean, defaultExecutionMode?: ExecutionEnvironmentMode): ExecutionEnvironmentMode;
|
|
61
|
+
export declare function resolveWorkspaceCwd(): string;
|
|
97
62
|
interface CodexEffectiveRuntimePolicy {
|
|
98
63
|
model?: string;
|
|
99
64
|
permissionMode?: string;
|
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 { 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, 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,
|
|
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, releaseConversationEnvironment, resolveLocalRuntimeSessionState, resolveCanonAgent, verifyResolvedAgentEnvironment, CanonApiError, loadRuntimeSessionState, sendMessageWithRetry, sendMessageWithRetryChunked, saveRuntimeSessionState, publishHostAgentRuntime, publishHostSessionSnapshots, renderCanonHostInboundContent, renderCodingHostInboundPrompt, resolveConfiguredWorkspaceCwd, 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';
|
|
@@ -69,36 +69,14 @@ export function buildCodexInitialSessionState(input) {
|
|
|
69
69
|
state: 'idle',
|
|
70
70
|
};
|
|
71
71
|
}
|
|
72
|
-
export function buildCodexLiveSessionConfig(input) {
|
|
73
|
-
return {
|
|
74
|
-
...(input.model ? { model: input.model } : {}),
|
|
75
|
-
...(input.permissionMode ? { permissionMode: input.permissionMode } : {}),
|
|
76
|
-
...(input.effort ? { effort: input.effort } : {}),
|
|
77
|
-
...(input.workspaceId ? { workspaceId: input.workspaceId } : {}),
|
|
78
|
-
executionMode: input.executionMode,
|
|
79
|
-
executionBranch: input.executionBranch ?? null,
|
|
80
|
-
};
|
|
81
|
-
}
|
|
82
|
-
export function buildCodexServiceSnapshotConfig(input) {
|
|
83
|
-
// Keep every setup key present, including keys whose host value is
|
|
84
|
-
// undefined. `publishHostSessionSnapshots` merges this over any persisted
|
|
85
|
-
// coding config, so an absent host value clears rather than resurrects a
|
|
86
|
-
// stale member-selected value.
|
|
87
|
-
return {
|
|
88
|
-
model: input.model,
|
|
89
|
-
permissionMode: input.permissionMode,
|
|
90
|
-
effort: input.effort,
|
|
91
|
-
workspaceId: input.workspaceId,
|
|
92
|
-
executionMode: 'locked',
|
|
93
|
-
executionBranch: null,
|
|
94
|
-
};
|
|
95
|
-
}
|
|
96
72
|
const MAX_SESSIONS = 12;
|
|
97
73
|
export function createCodexRecoveryCheckpointTracker(persist) {
|
|
98
74
|
return createRecoveryCheckpointTracker(persist);
|
|
99
75
|
}
|
|
100
76
|
// IDLE_TIMEOUT_MS (30 minutes) is shared with the other coding-agent hosts.
|
|
101
77
|
const HEARTBEAT_MS = 30_000;
|
|
78
|
+
const LOCAL_CONFIGURATION_REQUIRED_MESSAGE = 'This agent needs local runtime configuration before it can start this conversation. '
|
|
79
|
+
+ 'Its operator must configure it and retry.';
|
|
102
80
|
/** How Codex says it finished the work but Canon would not take the answer. */
|
|
103
81
|
const CODEX_UNDELIVERABLE_FINAL_WORDING = {
|
|
104
82
|
lead: 'The Codex host completed the turn, but Canon could not deliver the reply',
|
|
@@ -113,18 +91,13 @@ const CODEX_RUNTIME_CAPABILITIES = {
|
|
|
113
91
|
supportsNonFinalPermanentMessages: false,
|
|
114
92
|
};
|
|
115
93
|
let workingDir = process.cwd();
|
|
116
|
-
/**
|
|
117
|
-
* Agent-developer setting, resolved once at startup. Deliberately NOT read from
|
|
118
|
-
* `/session-config`: that path is the USER's per-conversation control plane,
|
|
119
|
-
* and owner ruling 5 puts turn verbosity outside user control.
|
|
120
|
-
*/
|
|
94
|
+
/** Turn verbosity is local agent-developer configuration, resolved at startup. */
|
|
121
95
|
let configuredTurnVerbosity = null;
|
|
122
96
|
let workspaceOptions = [];
|
|
123
97
|
let workspaceRoots = [];
|
|
124
98
|
let workspaceRootMetadata = [];
|
|
125
99
|
/** Conservative fallback used only when native app-server discovery is unavailable. */
|
|
126
100
|
export const CODEX_EFFORT_OPTIONS = FALLBACK_CODEX_EFFORT_OPTIONS;
|
|
127
|
-
export const CODEX_SESSION_CONFIG_FIELDS = ['permissionMode', 'effort'];
|
|
128
101
|
const MAX_CODEX_SKILL_COMMAND_CHOICES = 50;
|
|
129
102
|
export function buildCodexSkillCommands(skills) {
|
|
130
103
|
return skills
|
|
@@ -283,18 +256,10 @@ export function getCodexRequestingUserId(message) {
|
|
|
283
256
|
async function publishAgentRuntime(agentId, runtime, rtdb) {
|
|
284
257
|
await publishHostAgentRuntime(agentId, 'codex', runtime, rtdb);
|
|
285
258
|
}
|
|
286
|
-
|
|
287
|
-
return loadHostSessionConfig({
|
|
288
|
-
conversationId,
|
|
289
|
-
agentId,
|
|
290
|
-
rtdb,
|
|
291
|
-
extraStringFields: CODEX_SESSION_CONFIG_FIELDS,
|
|
292
|
-
});
|
|
293
|
-
}
|
|
294
|
-
export function resolveSessionExecutionMode(config, serviceAgentMode = false, defaultExecutionMode = 'worktree') {
|
|
259
|
+
export function resolveSessionExecutionMode(serviceAgentMode = false, defaultExecutionMode = 'worktree') {
|
|
295
260
|
if (serviceAgentMode)
|
|
296
261
|
return 'locked';
|
|
297
|
-
return
|
|
262
|
+
return defaultExecutionMode;
|
|
298
263
|
}
|
|
299
264
|
function resolveConfiguredDefaultExecutionMode(value) {
|
|
300
265
|
if (value == null || value === '')
|
|
@@ -303,13 +268,10 @@ function resolveConfiguredDefaultExecutionMode(value) {
|
|
|
303
268
|
return value;
|
|
304
269
|
throw new Error('--default-execution-mode must be worktree or locked');
|
|
305
270
|
}
|
|
306
|
-
export function
|
|
307
|
-
return
|
|
308
|
-
}
|
|
309
|
-
export function resolveWorkspaceCwd(config, serviceAgentMode = false) {
|
|
310
|
-
return resolveHostWorkspaceCwd({
|
|
271
|
+
export function resolveWorkspaceCwd() {
|
|
272
|
+
return resolveConfiguredWorkspaceCwd({
|
|
311
273
|
workspaceOptions,
|
|
312
|
-
|
|
274
|
+
workspaceId: workspaceOptions[0]?.id,
|
|
313
275
|
defaultCwd: workingDir,
|
|
314
276
|
});
|
|
315
277
|
}
|
|
@@ -736,7 +698,7 @@ export async function main() {
|
|
|
736
698
|
}
|
|
737
699
|
console.error(`[canon-codex] Authenticated as ${agentId}`);
|
|
738
700
|
}
|
|
739
|
-
|
|
701
|
+
let codexDynamicTools = filterCodexCommunicationTools(baseCodexDynamicTools, outboundPolicy);
|
|
740
702
|
// Shared poll/timeout engine. Built-in `input`/`card` descriptors own
|
|
741
703
|
// create+poll (codex passes native/responder policy via payload/options).
|
|
742
704
|
// Approval keeps codex's own resolution shape (no owner-authored outcome
|
|
@@ -930,30 +892,8 @@ export async function main() {
|
|
|
930
892
|
});
|
|
931
893
|
}
|
|
932
894
|
function writeState(session) {
|
|
933
|
-
const appliedAt = Date.now();
|
|
934
|
-
const controlState = {};
|
|
935
|
-
if (session.state.model !== undefined) {
|
|
936
|
-
controlState.model = { value: session.state.model, source: 'applied', appliedAt };
|
|
937
|
-
}
|
|
938
|
-
if (session.state.permissionMode !== undefined) {
|
|
939
|
-
controlState.permissionMode = { value: session.state.permissionMode, source: 'applied', appliedAt };
|
|
940
|
-
}
|
|
941
|
-
if (session.state.effort !== undefined) {
|
|
942
|
-
controlState.effort = { value: session.state.effort, source: 'applied', appliedAt };
|
|
943
|
-
}
|
|
944
895
|
runtimeState.writeSessionState(session.conversationId, {
|
|
945
896
|
lastError: session.state.lastError,
|
|
946
|
-
model: session.state.model,
|
|
947
|
-
permissionMode: session.state.permissionMode,
|
|
948
|
-
effort: session.state.effort,
|
|
949
|
-
controlState,
|
|
950
|
-
cwd: session.cwd,
|
|
951
|
-
executionMode: session.environment.mode,
|
|
952
|
-
...(session.environment.branch ? { executionBranch: session.environment.branch } : {}),
|
|
953
|
-
...(session.environment.worktreePath ? { worktreePath: session.environment.worktreePath } : {}),
|
|
954
|
-
...(resolveExecutionFallbackReason(session.environment)
|
|
955
|
-
? { executionFallbackReason: resolveExecutionFallbackReason(session.environment) ?? undefined }
|
|
956
|
-
: {}),
|
|
957
897
|
hostMode: true,
|
|
958
898
|
clientType: 'codex',
|
|
959
899
|
isActive: true,
|
|
@@ -1224,6 +1164,7 @@ export async function main() {
|
|
|
1224
1164
|
session.currentTurnOpenedAt = null;
|
|
1225
1165
|
session.currentTurnUpdatedAt = null;
|
|
1226
1166
|
session.currentTurnCanUseCodexAppTools = false;
|
|
1167
|
+
session.currentReplyAuthority = null;
|
|
1227
1168
|
session.currentTurnSilenced = false;
|
|
1228
1169
|
session.lastAcceptedIntent = null;
|
|
1229
1170
|
session.resetRequested = false;
|
|
@@ -1261,20 +1202,47 @@ export async function main() {
|
|
|
1261
1202
|
evictOldestIdle();
|
|
1262
1203
|
}
|
|
1263
1204
|
const creation = (async () => {
|
|
1264
|
-
const
|
|
1265
|
-
const
|
|
1266
|
-
|
|
1267
|
-
const environment = prepareConversationEnvironment({
|
|
1205
|
+
const sessionExecutionMode = resolveSessionExecutionMode(serviceAgentMode, defaultExecutionMode);
|
|
1206
|
+
const workspaceCwd = resolveWorkspaceCwd();
|
|
1207
|
+
let environment = prepareConversationEnvironment({
|
|
1268
1208
|
agentId,
|
|
1269
1209
|
conversationId,
|
|
1270
1210
|
workspaceCwd,
|
|
1271
1211
|
allowWorktrees: sessionExecutionMode === 'worktree',
|
|
1272
1212
|
});
|
|
1273
1213
|
try {
|
|
1214
|
+
const persistedMapping = resolveLocalRuntimeSessionState(runtimeId, {
|
|
1215
|
+
conversationId,
|
|
1216
|
+
baseCwd: environment.baseCwd,
|
|
1217
|
+
executionMode: environment.mode,
|
|
1218
|
+
resumeField: 'threadId',
|
|
1219
|
+
configuredBaseCwds: workspaceOptions.map((workspace) => workspace.cwd),
|
|
1220
|
+
availableExecutionModes: hostAvailableExecutionModes,
|
|
1221
|
+
});
|
|
1222
|
+
if (persistedMapping.status === 'configuration_required') {
|
|
1223
|
+
throw new ExecutionEnvironmentError(persistedMapping.message, LOCAL_CONFIGURATION_REQUIRED_MESSAGE);
|
|
1224
|
+
}
|
|
1225
|
+
if (persistedMapping.status === 'adopted'
|
|
1226
|
+
&& (persistedMapping.state.baseCwd !== environment.baseCwd
|
|
1227
|
+
|| persistedMapping.state.executionMode !== environment.mode)) {
|
|
1228
|
+
const restoredEnvironment = prepareConversationEnvironment({
|
|
1229
|
+
agentId,
|
|
1230
|
+
conversationId,
|
|
1231
|
+
workspaceCwd: persistedMapping.state.baseCwd,
|
|
1232
|
+
allowWorktrees: persistedMapping.state.executionMode === 'worktree',
|
|
1233
|
+
});
|
|
1234
|
+
if (restoredEnvironment.mode !== persistedMapping.state.executionMode) {
|
|
1235
|
+
releaseConversationEnvironment(restoredEnvironment);
|
|
1236
|
+
throw new ExecutionEnvironmentError(`Conversation ${conversationId} requires local execution mode ${persistedMapping.state.executionMode}, but workspace ${persistedMapping.state.baseCwd} can only be opened in ${restoredEnvironment.mode} mode.`, LOCAL_CONFIGURATION_REQUIRED_MESSAGE);
|
|
1237
|
+
}
|
|
1238
|
+
releaseConversationEnvironment(environment);
|
|
1239
|
+
environment = restoredEnvironment;
|
|
1240
|
+
console.error(`[canon-codex] [${conversationId.slice(0, 8)}] Restoring saved local runtime → ${environment.mode} (${environment.baseCwd})`);
|
|
1241
|
+
}
|
|
1274
1242
|
const sessionCwd = environment.cwd;
|
|
1275
1243
|
const policy = resolveCodexEffectiveRuntimePolicy({
|
|
1276
1244
|
args,
|
|
1277
|
-
config,
|
|
1245
|
+
config: null,
|
|
1278
1246
|
permissionEnvelope: codexPermissionEnvelope,
|
|
1279
1247
|
environment,
|
|
1280
1248
|
serviceAgentMode,
|
|
@@ -1283,12 +1251,17 @@ export async function main() {
|
|
|
1283
1251
|
if (modelGuard) {
|
|
1284
1252
|
throw new ExecutionEnvironmentError(modelGuard, modelGuard);
|
|
1285
1253
|
}
|
|
1286
|
-
const storedThreadId = loadStoredThreadId(runtimeId, conversationId, environment.baseCwd, environment.mode, policy.fingerprint
|
|
1254
|
+
const storedThreadId = loadStoredThreadId(runtimeId, conversationId, environment.baseCwd, environment.mode, policy.fingerprint, {
|
|
1255
|
+
...(persistedMapping.status === 'new' || !persistedMapping.state.workspaceId
|
|
1256
|
+
? {}
|
|
1257
|
+
: { workspaceId: persistedMapping.state.workspaceId }),
|
|
1258
|
+
allowLegacyPolicyMigration: persistedMapping.status !== 'new',
|
|
1259
|
+
});
|
|
1287
1260
|
const effectiveModel = policy.model ?? codexDefaultModel;
|
|
1288
1261
|
const initialEffortResolution = resolveCodexEffortForModel({
|
|
1289
1262
|
models: codexModels,
|
|
1290
1263
|
model: effectiveModel,
|
|
1291
|
-
requestedEffort:
|
|
1264
|
+
requestedEffort: configuredCodexEffort,
|
|
1292
1265
|
});
|
|
1293
1266
|
const initialEffort = initialEffortResolution.value;
|
|
1294
1267
|
const adapter = useAppServer
|
|
@@ -1336,6 +1309,7 @@ export async function main() {
|
|
|
1336
1309
|
currentTurnOpenedAt: null,
|
|
1337
1310
|
currentTurnUpdatedAt: null,
|
|
1338
1311
|
currentTurnCanUseCodexAppTools: false,
|
|
1312
|
+
currentReplyAuthority: null,
|
|
1339
1313
|
currentTurnAbortController: null,
|
|
1340
1314
|
// Corrected by the first turn that runs; a session with no turn
|
|
1341
1315
|
// publishes nothing anyway, and quiet is never an accident.
|
|
@@ -1353,6 +1327,10 @@ export async function main() {
|
|
|
1353
1327
|
};
|
|
1354
1328
|
sessions.set(conversationId, session);
|
|
1355
1329
|
await controlPoller.baseline([conversationId]);
|
|
1330
|
+
await runtimeState.patchAgentSessionSnapshot(conversationId, {
|
|
1331
|
+
configurationStatus: 'ready',
|
|
1332
|
+
lastError: null,
|
|
1333
|
+
});
|
|
1356
1334
|
console.error(`[canon-codex] [${conversationId.slice(0, 8)}] Environment → ${environment.mode} (${sessionCwd})`);
|
|
1357
1335
|
writeState(session);
|
|
1358
1336
|
writeTurn(session);
|
|
@@ -1384,6 +1362,7 @@ export async function main() {
|
|
|
1384
1362
|
canUseCodexAppTools: turn.canUseCodexAppTools ?? false,
|
|
1385
1363
|
...(turn.turnVerbosity ? { turnVerbosity: turn.turnVerbosity } : {}),
|
|
1386
1364
|
requestingUserId: turn.requestingUserId ?? null,
|
|
1365
|
+
replyAuthority: turn.replyAuthority ?? null,
|
|
1387
1366
|
};
|
|
1388
1367
|
if (toFront) {
|
|
1389
1368
|
session.queue.unshift(nextPrompt);
|
|
@@ -1454,6 +1433,9 @@ export async function main() {
|
|
|
1454
1433
|
if (!(session.adapter instanceof CodexAppServerAdapter)) {
|
|
1455
1434
|
return deniedCodexAppToolResult('This Codex transport does not support dynamic tools.');
|
|
1456
1435
|
}
|
|
1436
|
+
if (outboundPolicy !== 'open' && outboundPolicy !== 'approval-required') {
|
|
1437
|
+
return deniedCodexAppToolResult('Outbound communication is not enabled by the agent operator.');
|
|
1438
|
+
}
|
|
1457
1439
|
return handleCodexCommunicateToolCall(client, params);
|
|
1458
1440
|
}
|
|
1459
1441
|
if (serviceAgentMode && !isCodexServiceAgentToolCall(params)) {
|
|
@@ -1588,6 +1570,8 @@ export async function main() {
|
|
|
1588
1570
|
currentThreadId: session.adapter.getThreadId(),
|
|
1589
1571
|
currentCwd: session.cwd,
|
|
1590
1572
|
workspaces: workspaceOptions,
|
|
1573
|
+
communicationEnabled: outboundPolicy === 'open'
|
|
1574
|
+
|| outboundPolicy === 'approval-required',
|
|
1591
1575
|
model: session.state.model ?? null,
|
|
1592
1576
|
effort: session.state.effort ?? null,
|
|
1593
1577
|
}, params);
|
|
@@ -1878,16 +1862,22 @@ export async function main() {
|
|
|
1878
1862
|
}
|
|
1879
1863
|
catch (error) {
|
|
1880
1864
|
const message = error instanceof Error ? error.message : String(error);
|
|
1881
|
-
const userMessage = error instanceof ExecutionEnvironmentError ? error.userMessage : message;
|
|
1882
1865
|
console.error(`[canon-codex] [${input.conversationId.slice(0, 8)}] Failed to create session: ${message}`);
|
|
1866
|
+
await runtimeState.patchAgentSessionSnapshot(input.conversationId, {
|
|
1867
|
+
configurationStatus: 'configuration_required',
|
|
1868
|
+
lastError: LOCAL_CONFIGURATION_REQUIRED_MESSAGE,
|
|
1869
|
+
}).catch(() => { });
|
|
1883
1870
|
await markQueuedMessageAccepted(input.conversationId, input.message.id, shouldMarkAccepted);
|
|
1884
|
-
await sendMessageWithRetryChunked(client, input.conversationId,
|
|
1871
|
+
await sendMessageWithRetryChunked(client, input.conversationId, LOCAL_CONFIGURATION_REQUIRED_MESSAGE, {
|
|
1885
1872
|
messageId: `codex-start-failed-${input.message.id}`,
|
|
1886
1873
|
...(activeSelfContextId ? { selfContextId: activeSelfContextId } : {}),
|
|
1887
1874
|
metadata: {
|
|
1875
|
+
turnId: `codex-start:${input.message.id}`,
|
|
1876
|
+
runtimeStatus: 'configuration_required',
|
|
1888
1877
|
turnSemantics: 'turn_complete',
|
|
1889
1878
|
replyBehavior: 'suppress_auto_reply',
|
|
1890
1879
|
},
|
|
1880
|
+
...(input.replyAuthority ? { replyAuthority: input.replyAuthority } : {}),
|
|
1891
1881
|
}).catch(() => { });
|
|
1892
1882
|
recoveryCheckpointsFor(input.conversationId).settle(input.message.id);
|
|
1893
1883
|
return;
|
|
@@ -1906,7 +1896,10 @@ export async function main() {
|
|
|
1906
1896
|
...(useAppServer ? { noReplyToolName: CODEX_NO_REPLY_MODEL_TOOL_NAME } : {}),
|
|
1907
1897
|
});
|
|
1908
1898
|
if (session.running && deliveryIntent === 'interrupt') {
|
|
1909
|
-
enqueuePrompt(session, prompt, deliveryIntent, true, input.message.id, shouldMarkAccepted, imagePaths, mediaAddDirs, planCommand.planMode,
|
|
1899
|
+
enqueuePrompt(session, prompt, deliveryIntent, true, input.message.id, shouldMarkAccepted, imagePaths, mediaAddDirs, planCommand.planMode, {
|
|
1900
|
+
...resolveCodexTurnModes(participantContext, input.message),
|
|
1901
|
+
replyAuthority: input.replyAuthority ?? null,
|
|
1902
|
+
});
|
|
1910
1903
|
console.error(`[canon-codex] [${input.conversationId.slice(0, 8)}] Interrupting current turn for explicit human send-now`);
|
|
1911
1904
|
session.currentTurnAbortController?.abort(new Error('Codex turn interrupted by a newer message'));
|
|
1912
1905
|
await session.adapter.interrupt().catch(() => { });
|
|
@@ -1914,10 +1907,14 @@ export async function main() {
|
|
|
1914
1907
|
typingSignals.clear(input.conversationId).catch(() => { });
|
|
1915
1908
|
return;
|
|
1916
1909
|
}
|
|
1917
|
-
enqueuePrompt(session, prompt, deliveryIntent, false, input.message.id, shouldMarkAccepted, imagePaths, mediaAddDirs, planCommand.planMode,
|
|
1910
|
+
enqueuePrompt(session, prompt, deliveryIntent, false, input.message.id, shouldMarkAccepted, imagePaths, mediaAddDirs, planCommand.planMode, {
|
|
1911
|
+
...resolveCodexTurnModes(participantContext, input.message),
|
|
1912
|
+
replyAuthority: input.replyAuthority ?? null,
|
|
1913
|
+
});
|
|
1918
1914
|
}
|
|
1919
1915
|
function sendTurnArtifactFile(session, file) {
|
|
1920
1916
|
return sendMediaFileMessage(client, session.conversationId, file.path, '', {
|
|
1917
|
+
...(session.currentReplyAuthority ? { replyAuthority: session.currentReplyAuthority } : {}),
|
|
1921
1918
|
...(session.activeSelfContextId ? { selfContextId: session.activeSelfContextId } : {}),
|
|
1922
1919
|
metadata: {
|
|
1923
1920
|
...(session.currentTurnId ? { turnId: session.currentTurnId } : {}),
|
|
@@ -1945,6 +1942,7 @@ export async function main() {
|
|
|
1945
1942
|
session.currentTurnOpenedAt = Date.now();
|
|
1946
1943
|
session.currentTurnUpdatedAt = session.currentTurnOpenedAt;
|
|
1947
1944
|
session.currentTurnCanUseCodexAppTools = nextTurn.canUseCodexAppTools === true;
|
|
1945
|
+
session.currentReplyAuthority = nextTurn.replyAuthority;
|
|
1948
1946
|
session.currentTurnAbortController = new AbortController();
|
|
1949
1947
|
// A continuation prompt (a plan-review result) carries none, and keeps the
|
|
1950
1948
|
// conversation's last answer rather than silently reverting to verbose.
|
|
@@ -2249,6 +2247,9 @@ export async function main() {
|
|
|
2249
2247
|
const turnTrail = buildFinalTurnTrail(session);
|
|
2250
2248
|
await sendMessageWithRetryChunked(client, session.conversationId, result.finalMessage, {
|
|
2251
2249
|
messageId: buildCodexMessageId(session, 'final'),
|
|
2250
|
+
...(session.currentReplyAuthority
|
|
2251
|
+
? { replyAuthority: session.currentReplyAuthority }
|
|
2252
|
+
: {}),
|
|
2252
2253
|
...(session.activeSelfContextId
|
|
2253
2254
|
? { selfContextId: session.activeSelfContextId }
|
|
2254
2255
|
: {}),
|
|
@@ -2274,6 +2275,9 @@ export async function main() {
|
|
|
2274
2275
|
const turnTrail = buildFinalTurnTrail(session);
|
|
2275
2276
|
await sendMessageWithRetryChunked(client, session.conversationId, userVisibleError, {
|
|
2276
2277
|
messageId: buildCodexMessageId(session, 'error'),
|
|
2278
|
+
...(session.currentReplyAuthority
|
|
2279
|
+
? { replyAuthority: session.currentReplyAuthority }
|
|
2280
|
+
: {}),
|
|
2277
2281
|
...(session.activeSelfContextId
|
|
2278
2282
|
? { selfContextId: session.activeSelfContextId }
|
|
2279
2283
|
: {}),
|
|
@@ -2330,6 +2334,9 @@ export async function main() {
|
|
|
2330
2334
|
await routeArtifactsOnce();
|
|
2331
2335
|
await sendMessageWithRetryChunked(client, session.conversationId, message, {
|
|
2332
2336
|
messageId: buildCodexMessageId(session, 'failure'),
|
|
2337
|
+
...(session.currentReplyAuthority
|
|
2338
|
+
? { replyAuthority: session.currentReplyAuthority }
|
|
2339
|
+
: {}),
|
|
2333
2340
|
...(session.activeSelfContextId
|
|
2334
2341
|
? { selfContextId: session.activeSelfContextId }
|
|
2335
2342
|
: {}),
|
|
@@ -2358,6 +2365,7 @@ export async function main() {
|
|
|
2358
2365
|
session.currentTurnOpenedAt = null;
|
|
2359
2366
|
session.currentTurnUpdatedAt = null;
|
|
2360
2367
|
session.currentTurnCanUseCodexAppTools = false;
|
|
2368
|
+
session.currentReplyAuthority = null;
|
|
2361
2369
|
session.currentTurnSilenced = false;
|
|
2362
2370
|
session.lastAcceptedIntent = null;
|
|
2363
2371
|
session.resetRequested = false;
|
|
@@ -2486,64 +2494,6 @@ export async function main() {
|
|
|
2486
2494
|
probe.close();
|
|
2487
2495
|
}
|
|
2488
2496
|
}
|
|
2489
|
-
function applySessionControl(conversationId, control) {
|
|
2490
|
-
const session = sessions.get(conversationId);
|
|
2491
|
-
if (!session || session.closed)
|
|
2492
|
-
return;
|
|
2493
|
-
if (serviceAgentMode) {
|
|
2494
|
-
console.error(`[canon-codex] [${conversationId.slice(0, 8)}] Ignoring user session controls for service-agent runtime`);
|
|
2495
|
-
writeState(session);
|
|
2496
|
-
return;
|
|
2497
|
-
}
|
|
2498
|
-
let modelChanged = false;
|
|
2499
|
-
if (control.model && control.model !== session.state.model) {
|
|
2500
|
-
if (codexModelOptions.length > 0 && !codexModelOptions.some((option) => option.value === control.model)) {
|
|
2501
|
-
console.error(`[canon-codex] [${conversationId.slice(0, 8)}] Ignoring model outside the discovered Codex catalog (${control.model})`);
|
|
2502
|
-
writeState(session);
|
|
2503
|
-
return;
|
|
2504
|
-
}
|
|
2505
|
-
const modelGuard = buildCodexModelGuardMessage(control.model, codexCliStatus);
|
|
2506
|
-
if (modelGuard) {
|
|
2507
|
-
session.state.lastError = modelGuard;
|
|
2508
|
-
console.error(`[canon-codex] [${conversationId.slice(0, 8)}] ${modelGuard}`);
|
|
2509
|
-
writeState(session);
|
|
2510
|
-
// The poller consumes the node; skip effort handling for this pass,
|
|
2511
|
-
// matching the legacy loop.
|
|
2512
|
-
return;
|
|
2513
|
-
}
|
|
2514
|
-
session.adapter.setModel(control.model);
|
|
2515
|
-
session.state.model = control.model;
|
|
2516
|
-
modelChanged = true;
|
|
2517
|
-
console.error(`[canon-codex] [${conversationId.slice(0, 8)}] Model set for next turn -> ${control.model}`);
|
|
2518
|
-
}
|
|
2519
|
-
if (control.permissionMode) {
|
|
2520
|
-
console.error(`[canon-codex] [${conversationId.slice(0, 8)}] approval mode is session-creation-only; ignoring mid-session change request (${control.permissionMode})`);
|
|
2521
|
-
// Convergence contract: a consumed session control must always be
|
|
2522
|
-
// answered. Re-publish the currently applied state so clients settle on
|
|
2523
|
-
// the authoritative value instead of holding the composer until timeout.
|
|
2524
|
-
}
|
|
2525
|
-
if (control.effort || modelChanged) {
|
|
2526
|
-
const effortResolution = resolveCodexEffortForModel({
|
|
2527
|
-
models: codexModels,
|
|
2528
|
-
model: session.state.model,
|
|
2529
|
-
requestedEffort: control.effort ?? session.state.effort,
|
|
2530
|
-
});
|
|
2531
|
-
if (effortResolution.value !== session.state.effort) {
|
|
2532
|
-
session.adapter.setReasoningEffort(effortResolution.value);
|
|
2533
|
-
session.state.effort = effortResolution.value ?? undefined;
|
|
2534
|
-
}
|
|
2535
|
-
if (control.effort && !effortResolution.accepted) {
|
|
2536
|
-
console.error(`[canon-codex] [${conversationId.slice(0, 8)}] ${control.effort} is unsupported by ${session.state.model ?? 'the active model'}; reset to ${effortResolution.value ?? 'the model default'}`);
|
|
2537
|
-
}
|
|
2538
|
-
else if (control.effort) {
|
|
2539
|
-
console.error(`[canon-codex] [${conversationId.slice(0, 8)}] Reasoning effort set for next turn -> ${control.effort}`);
|
|
2540
|
-
}
|
|
2541
|
-
else if (modelChanged && effortResolution.value) {
|
|
2542
|
-
console.error(`[canon-codex] [${conversationId.slice(0, 8)}] Reasoning effort for ${session.state.model} -> ${effortResolution.value}`);
|
|
2543
|
-
}
|
|
2544
|
-
}
|
|
2545
|
-
writeState(session);
|
|
2546
|
-
}
|
|
2547
2497
|
async function handleControlSignal(event) {
|
|
2548
2498
|
const { conversationId, type } = event;
|
|
2549
2499
|
const session = sessions.get(conversationId);
|
|
@@ -2609,9 +2559,6 @@ export async function main() {
|
|
|
2609
2559
|
conversationIds: () => sessions.keys(),
|
|
2610
2560
|
hasActiveWork: () => [...sessions.values()].some((session) => !session.closed
|
|
2611
2561
|
&& (session.running || session.queue.length > 0 || session.turnState === 'waiting_input')),
|
|
2612
|
-
onSessionControl: ({ conversationId, control }) => {
|
|
2613
|
-
applySessionControl(conversationId, control);
|
|
2614
|
-
},
|
|
2615
2562
|
onSignal: handleControlSignal,
|
|
2616
2563
|
onPrimitive: handleControlPrimitive,
|
|
2617
2564
|
onError: (error) => {
|
|
@@ -2647,41 +2594,6 @@ export async function main() {
|
|
|
2647
2594
|
agentId,
|
|
2648
2595
|
rtdb,
|
|
2649
2596
|
clientType: 'codex',
|
|
2650
|
-
runtime: runtimeDescriptor,
|
|
2651
|
-
workspaceOptions,
|
|
2652
|
-
defaultCwd: workingDir,
|
|
2653
|
-
extraSessionConfigFields: CODEX_SESSION_CONFIG_FIELDS,
|
|
2654
|
-
liveSessionConfigByConversation: new Map(Array.from(serviceAgentMode ? knownConversationIds : sessions.keys()).map((conversationId) => {
|
|
2655
|
-
const session = sessions.get(conversationId);
|
|
2656
|
-
if (serviceAgentMode) {
|
|
2657
|
-
return [
|
|
2658
|
-
conversationId,
|
|
2659
|
-
buildCodexServiceSnapshotConfig({
|
|
2660
|
-
model: codexDefaultModel ?? session?.state.model ?? undefined,
|
|
2661
|
-
permissionMode: codexPermissionEnvelope.defaultPermissionMode,
|
|
2662
|
-
effort: codexDefaultEffort ?? session?.state.effort ?? undefined,
|
|
2663
|
-
workspaceId: resolveWorkspaceIdForBaseCwd(workingDir) ?? undefined,
|
|
2664
|
-
}),
|
|
2665
|
-
];
|
|
2666
|
-
}
|
|
2667
|
-
if (!session) {
|
|
2668
|
-
// Non-service snapshots are published only for live sessions,
|
|
2669
|
-
// but keep this branch total if that invariant changes.
|
|
2670
|
-
return [conversationId, {}];
|
|
2671
|
-
}
|
|
2672
|
-
const workspaceId = resolveWorkspaceIdForBaseCwd(session.environment.baseCwd);
|
|
2673
|
-
return [
|
|
2674
|
-
conversationId,
|
|
2675
|
-
buildCodexLiveSessionConfig({
|
|
2676
|
-
model: session.state.model,
|
|
2677
|
-
permissionMode: session.state.permissionMode,
|
|
2678
|
-
effort: session.state.effort,
|
|
2679
|
-
workspaceId,
|
|
2680
|
-
executionMode: session.environment.mode,
|
|
2681
|
-
executionBranch: session.environment.branch ?? null,
|
|
2682
|
-
}),
|
|
2683
|
-
];
|
|
2684
|
-
})),
|
|
2685
2597
|
}).catch((error) => {
|
|
2686
2598
|
console.error('[canon-codex] Failed to publish session snapshots:', error);
|
|
2687
2599
|
});
|
|
@@ -2865,6 +2777,7 @@ export async function main() {
|
|
|
2865
2777
|
selfContexts: payload.selfContexts,
|
|
2866
2778
|
provenance: payload.provenance,
|
|
2867
2779
|
turnDispatch: payload.turnDispatch,
|
|
2780
|
+
replyAuthority: payload.replyAuthority,
|
|
2868
2781
|
}).then(() => settleInboundMessageId(message.id, true), (error) => {
|
|
2869
2782
|
settleInboundMessageId(message.id, false);
|
|
2870
2783
|
console.error(`[canon-codex] [${payload.conversationId.slice(0, 8)}] Failed to queue inbound message:`, error instanceof Error ? error.message : error);
|
|
@@ -2876,6 +2789,12 @@ export async function main() {
|
|
|
2876
2789
|
onConversationUpdated: (payload) => {
|
|
2877
2790
|
handleConversationUpdated(payload);
|
|
2878
2791
|
},
|
|
2792
|
+
onAgentContext: (context) => {
|
|
2793
|
+
ownerId = context.ownerId;
|
|
2794
|
+
ownerName = context.ownerName;
|
|
2795
|
+
outboundPolicy = context.outboundPolicy;
|
|
2796
|
+
codexDynamicTools = filterCodexCommunicationTools(baseCodexDynamicTools, outboundPolicy);
|
|
2797
|
+
},
|
|
2879
2798
|
onConnected: () => {
|
|
2880
2799
|
streamConnected = true;
|
|
2881
2800
|
void publishRuntimeHeartbeat();
|
package/dist/register.js
CHANGED
|
@@ -12,6 +12,7 @@ REQUIRED
|
|
|
12
12
|
|
|
13
13
|
FLAGS
|
|
14
14
|
--profile <name> Local profile name in ~/.canon/agents.json
|
|
15
|
+
--agent-id <id> Reconnect this exact transferred agent identity
|
|
15
16
|
--environment <id> Canon environment ID (or CANON_ENVIRONMENT_ID; default: canon-prod-v1)
|
|
16
17
|
--base-url <url> Canon API base URL override
|
|
17
18
|
--stream-url <url> Canon stream URL override
|
|
@@ -23,6 +24,7 @@ FLAGS
|
|
|
23
24
|
EXAMPLES
|
|
24
25
|
canon-codex-register --name "My Codex" --description "Local coding agent" --phone "+15551234567"
|
|
25
26
|
canon-codex-register --name "Frontend" --description "React work" --phone "+15551234567" --profile frontend
|
|
27
|
+
canon-codex-register --name "Sold Agent" --description "Existing agent" --phone "+15551234567" --agent-id <id>
|
|
26
28
|
|
|
27
29
|
After approval, start it with CANON_AGENT=<profile> canon-codex --cwd /path/to/project.`;
|
|
28
30
|
const OPTIONS = {
|
package/dist/session-store.d.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { type ExecutionEnvironmentMode } from '@canonmsg/core';
|
|
2
|
+
export declare const CODEX_LOCAL_POLICY_FINGERPRINT_FORMAT: "local-v1";
|
|
2
3
|
export declare function buildCodexThreadPolicyFingerprint(input: {
|
|
3
4
|
baseCwd: string;
|
|
4
5
|
executionMode?: ExecutionEnvironmentMode;
|
|
@@ -10,6 +11,9 @@ export declare function buildCodexThreadPolicyFingerprint(input: {
|
|
|
10
11
|
fullAuto?: boolean;
|
|
11
12
|
bypassApprovalsAndSandbox?: boolean;
|
|
12
13
|
}): string;
|
|
13
|
-
export declare function loadStoredThreadId(runtimeId: string, conversationId: string, baseCwd: string, executionMode?: ExecutionEnvironmentMode, policyFingerprint?: string
|
|
14
|
+
export declare function loadStoredThreadId(runtimeId: string, conversationId: string, baseCwd: string, executionMode?: ExecutionEnvironmentMode, policyFingerprint?: string, options?: {
|
|
15
|
+
workspaceId?: string;
|
|
16
|
+
allowLegacyPolicyMigration?: boolean;
|
|
17
|
+
}): string | null;
|
|
14
18
|
export declare function saveStoredThreadId(runtimeId: string, conversationId: string, baseCwd: string, threadId: string, executionMode?: ExecutionEnvironmentMode, policyFingerprint?: string): void;
|
|
15
19
|
export declare function clearStoredThreadId(runtimeId: string, conversationId: string, baseCwd?: string, executionMode?: ExecutionEnvironmentMode): void;
|
package/dist/session-store.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { createHash } from 'node:crypto';
|
|
2
2
|
import { clearRuntimeSessionState, loadRuntimeSessionState, saveRuntimeSessionState, } from '@canonmsg/core';
|
|
3
|
+
export const CODEX_LOCAL_POLICY_FINGERPRINT_FORMAT = 'local-v1';
|
|
3
4
|
export function buildCodexThreadPolicyFingerprint(input) {
|
|
4
5
|
return createHash('sha256').update(JSON.stringify({
|
|
5
6
|
version: 2,
|
|
@@ -20,18 +21,24 @@ export function buildCodexThreadPolicyFingerprint(input) {
|
|
|
20
21
|
bypassApprovalsAndSandbox: input.bypassApprovalsAndSandbox === true,
|
|
21
22
|
})).digest('hex').slice(0, 24);
|
|
22
23
|
}
|
|
23
|
-
export function loadStoredThreadId(runtimeId, conversationId, baseCwd, executionMode, policyFingerprint) {
|
|
24
|
+
export function loadStoredThreadId(runtimeId, conversationId, baseCwd, executionMode, policyFingerprint, options = {}) {
|
|
24
25
|
const state = loadRuntimeSessionState(runtimeId, {
|
|
25
26
|
conversationId,
|
|
26
27
|
baseCwd,
|
|
27
28
|
executionMode,
|
|
29
|
+
workspaceId: options.workspaceId,
|
|
28
30
|
});
|
|
29
31
|
if (state?.threadId) {
|
|
30
32
|
if (policyFingerprint && state.codexPolicyFingerprint !== policyFingerprint) {
|
|
33
|
+
if (options.allowLegacyPolicyMigration === true
|
|
34
|
+
&& state.codexPolicyFingerprintFormat === undefined) {
|
|
35
|
+
return state.threadId;
|
|
36
|
+
}
|
|
31
37
|
clearRuntimeSessionState(runtimeId, {
|
|
32
38
|
conversationId,
|
|
33
39
|
baseCwd,
|
|
34
40
|
executionMode,
|
|
41
|
+
workspaceId: options.workspaceId,
|
|
35
42
|
});
|
|
36
43
|
return null;
|
|
37
44
|
}
|
|
@@ -45,7 +52,12 @@ export function saveStoredThreadId(runtimeId, conversationId, baseCwd, threadId,
|
|
|
45
52
|
baseCwd,
|
|
46
53
|
executionMode,
|
|
47
54
|
threadId,
|
|
48
|
-
...(policyFingerprint
|
|
55
|
+
...(policyFingerprint
|
|
56
|
+
? {
|
|
57
|
+
codexPolicyFingerprint: policyFingerprint,
|
|
58
|
+
codexPolicyFingerprintFormat: CODEX_LOCAL_POLICY_FINGERPRINT_FORMAT,
|
|
59
|
+
}
|
|
60
|
+
: {}),
|
|
49
61
|
});
|
|
50
62
|
}
|
|
51
63
|
export function clearStoredThreadId(runtimeId, conversationId, baseCwd, executionMode) {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@canonmsg/codex-plugin",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.29.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.
|
|
32
|
+
"@canonmsg/agent-sdk": "^10.0.0",
|
|
33
|
+
"@canonmsg/agent-tools": "^0.8.0",
|
|
34
34
|
"@canonmsg/coding-agent-host": "^0.7.0",
|
|
35
|
-
"@canonmsg/core": "^
|
|
36
|
-
"@canonmsg/rich-cards": "^0.10.
|
|
35
|
+
"@canonmsg/core": "^12.0.0",
|
|
36
|
+
"@canonmsg/rich-cards": "^0.10.4"
|
|
37
37
|
},
|
|
38
38
|
"engines": {
|
|
39
39
|
"node": ">=18.0.0"
|