@canonmsg/codex-plugin 0.19.2 → 0.20.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 +2 -2
- package/dist/app-server-adapter.d.ts +54 -3
- package/dist/app-server-adapter.js +1 -3
- package/dist/host.js +24 -82
- package/dist/permission-mode.d.ts +1 -1
- package/package.json +7 -7
- package/dist/adapter.d.ts +0 -97
- package/dist/adapter.js +0 -310
package/README.md
CHANGED
|
@@ -47,9 +47,9 @@ You do not need a git repo for host mode. The plugin passes `--skip-git-repo-che
|
|
|
47
47
|
- Interrupt by terminating the active Codex turn
|
|
48
48
|
- Tool/running status surfaced while Codex is working
|
|
49
49
|
|
|
50
|
-
##
|
|
50
|
+
## Transport
|
|
51
51
|
|
|
52
|
-
|
|
52
|
+
`canon-codex` drives Codex through the `codex app-server` JSON-RPC transport — the only supported transport. Canon routes native plan mode, runtime questions, approvals, tools, and live message deltas. A Codex CLI new enough to provide `codex app-server` is required.
|
|
53
53
|
|
|
54
54
|
Current Canon control truth for Codex host mode:
|
|
55
55
|
|
|
@@ -1,4 +1,57 @@
|
|
|
1
|
-
|
|
1
|
+
export type CodexSandboxMode = 'read-only' | 'workspace-write' | 'danger-full-access';
|
|
2
|
+
export type CodexApprovalPolicy = 'untrusted' | 'on-request' | 'never';
|
|
3
|
+
export type CodexEvent = {
|
|
4
|
+
type: 'thread.started';
|
|
5
|
+
threadId: string;
|
|
6
|
+
} | {
|
|
7
|
+
type: 'turn.started';
|
|
8
|
+
} | {
|
|
9
|
+
type: 'message';
|
|
10
|
+
text: string;
|
|
11
|
+
delta?: string;
|
|
12
|
+
itemId?: string;
|
|
13
|
+
} | {
|
|
14
|
+
type: 'plan.updated';
|
|
15
|
+
text: string;
|
|
16
|
+
} | {
|
|
17
|
+
type: 'waiting';
|
|
18
|
+
reason: string;
|
|
19
|
+
} | {
|
|
20
|
+
type: 'command.started';
|
|
21
|
+
command: string;
|
|
22
|
+
itemId?: string;
|
|
23
|
+
} | {
|
|
24
|
+
type: 'command.completed';
|
|
25
|
+
command: string;
|
|
26
|
+
output: string;
|
|
27
|
+
exitCode: number | null;
|
|
28
|
+
itemId?: string;
|
|
29
|
+
} | {
|
|
30
|
+
type: 'turn.completed';
|
|
31
|
+
usage?: {
|
|
32
|
+
input_tokens?: number;
|
|
33
|
+
cached_input_tokens?: number;
|
|
34
|
+
output_tokens?: number;
|
|
35
|
+
};
|
|
36
|
+
} | {
|
|
37
|
+
type: 'skills.changed';
|
|
38
|
+
};
|
|
39
|
+
export interface CodexServerRequest {
|
|
40
|
+
id: string | number;
|
|
41
|
+
method: string;
|
|
42
|
+
params: Record<string, unknown>;
|
|
43
|
+
}
|
|
44
|
+
export interface CodexRunTurnOptions {
|
|
45
|
+
planMode?: boolean;
|
|
46
|
+
onServerRequest?: (request: CodexServerRequest) => Promise<unknown>;
|
|
47
|
+
}
|
|
48
|
+
export interface CodexTurnResult {
|
|
49
|
+
threadId: string | null;
|
|
50
|
+
finalMessage: string | null;
|
|
51
|
+
exitCode: number | null;
|
|
52
|
+
interrupted: boolean;
|
|
53
|
+
errorText: string | null;
|
|
54
|
+
}
|
|
2
55
|
export type JsonRecord = Record<string, unknown>;
|
|
3
56
|
export interface CodexSkillMetadata {
|
|
4
57
|
name: string;
|
|
@@ -14,7 +67,6 @@ export declare class CodexAppServerAdapter {
|
|
|
14
67
|
private model;
|
|
15
68
|
private reasoningEffort;
|
|
16
69
|
private readonly sandbox;
|
|
17
|
-
private readonly legacyApprovalPolicy;
|
|
18
70
|
private readonly addDirs;
|
|
19
71
|
private readonly configOverrides;
|
|
20
72
|
private readonly fullAuto;
|
|
@@ -47,7 +99,6 @@ export declare class CodexAppServerAdapter {
|
|
|
47
99
|
model?: string | null;
|
|
48
100
|
reasoningEffort?: string | null;
|
|
49
101
|
sandbox?: CodexSandboxMode | null;
|
|
50
|
-
approvalPolicy?: CodexApprovalPolicy | null;
|
|
51
102
|
addDirs?: string[];
|
|
52
103
|
configOverrides?: string[];
|
|
53
104
|
fullAuto?: boolean;
|
|
@@ -6,7 +6,6 @@ export class CodexAppServerAdapter {
|
|
|
6
6
|
model;
|
|
7
7
|
reasoningEffort;
|
|
8
8
|
sandbox;
|
|
9
|
-
legacyApprovalPolicy;
|
|
10
9
|
addDirs;
|
|
11
10
|
configOverrides;
|
|
12
11
|
fullAuto;
|
|
@@ -39,7 +38,6 @@ export class CodexAppServerAdapter {
|
|
|
39
38
|
this.model = opts.model ?? null;
|
|
40
39
|
this.reasoningEffort = opts.reasoningEffort ?? null;
|
|
41
40
|
this.sandbox = opts.sandbox ?? null;
|
|
42
|
-
this.legacyApprovalPolicy = opts.approvalPolicy ?? null;
|
|
43
41
|
this.addDirs = opts.addDirs ?? [];
|
|
44
42
|
this.configOverrides = opts.configOverrides ?? [];
|
|
45
43
|
this.fullAuto = opts.fullAuto ?? false;
|
|
@@ -193,7 +191,7 @@ export class CodexAppServerAdapter {
|
|
|
193
191
|
resolveApprovalPolicy() {
|
|
194
192
|
if (this.bypassApprovalsAndSandbox || this.fullAuto)
|
|
195
193
|
return 'never';
|
|
196
|
-
return
|
|
194
|
+
return null;
|
|
197
195
|
}
|
|
198
196
|
configPayload() {
|
|
199
197
|
const config = {};
|
package/dist/host.js
CHANGED
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import { setDefaultResultOrder } from 'node:dns';
|
|
3
3
|
import { randomUUID } from 'node:crypto';
|
|
4
|
-
import { spawnSync } from 'node:child_process';
|
|
5
4
|
import { dirname } from 'node:path';
|
|
6
5
|
import { parseArgs } from 'node:util';
|
|
7
6
|
import { getCodexImagePath, inferUploadMimeType, materializeMessageMedia, materializeReplyContextMedia, } from '@canonmsg/agent-sdk';
|
|
@@ -16,8 +15,7 @@ import { STARTUP_RECOVERY_MAX_MESSAGES, STARTUP_RECOVERY_PAGE_SIZE, captureTurnA
|
|
|
16
15
|
import { RUNTIME_NEW_SESSION_ACTION, RUNTIME_STOP_ACTION, RUNTIME_STOP_AND_DROP_ACTION, buildRuntimeCardOutcome, buildPlanApprovalRequest, resolveQuestionAllowOther, buildCanonTurnContextV2, buildFirstPartyCodingRuntimeDescriptor, buildRuntimePresentationPolicy, buildCanonInboundFrameV1, DEFAULT_FIRST_PARTY_RUNTIME_PRESENTATION, EXECUTION_ENVIRONMENT_MODES, DEFAULT_RUNTIME_CAPABILITIES, normalizeTurnMetadata, parseRuntimeCardV1, shouldTriggerAgentTurn, renderCanonHostInboundContent, renderCodingHostInboundPrompt, } from '@canonmsg/core/contract';
|
|
17
16
|
import { ExecutionEnvironmentError, buildConfiguredWorkspaceOptionsWithRoots, buildLocalRuntimeId, buildPublicWorkspaceOptions, buildPublicWorkspaceRoots, heartbeatLocalRuntimeEntry, loadRuntimeSessionState, markLocalRuntimeStopped, prepareConversationEnvironment, releaseConversationEnvironment, resolveCanonAgent, saveRuntimeSessionState, upsertLocalRuntimeEntry, } from '@canonmsg/core/local';
|
|
18
17
|
import { readHostSessionConfig, resolveHostWorkspaceCwd, } from '@canonmsg/core/host';
|
|
19
|
-
import {
|
|
20
|
-
import { CodexAppServerAdapter } from './app-server-adapter.js';
|
|
18
|
+
import { CodexAppServerAdapter, } from './app-server-adapter.js';
|
|
21
19
|
import { CODEX_APP_DYNAMIC_TOOLS, deniedCodexAppToolResult, handleCodexAppToolCall, isCodexAppToolCall, } from './codex-app-tools.js';
|
|
22
20
|
import { mapCanonApprovalResultToCodexDecision, mapCodexAppServerApprovalRequest, } from './app-server-approval.js';
|
|
23
21
|
import { clearStoredThreadId, buildCodexThreadPolicyFingerprint, loadStoredThreadId, saveStoredThreadId, } from './session-store.js';
|
|
@@ -348,9 +346,7 @@ function resolveCodexEffectiveRuntimePolicy(input) {
|
|
|
348
346
|
}
|
|
349
347
|
const approvalOverride = mapCanonPermissionToCodex(permissionMode);
|
|
350
348
|
const defaultSandbox = (stringArg(input.args, 'sandbox') ?? null);
|
|
351
|
-
const defaultApprovalPolicy = (stringArg(input.args, 'ask-for-approval') ?? null);
|
|
352
349
|
const sandbox = approvalOverride ? approvalOverride.sandbox : defaultSandbox;
|
|
353
|
-
const approvalPolicy = approvalOverride ? null : defaultApprovalPolicy;
|
|
354
350
|
const fullAuto = approvalOverride ? approvalOverride.fullAuto : boolArg(input.args, 'full-auto');
|
|
355
351
|
const bypassApprovalsAndSandbox = approvalOverride
|
|
356
352
|
? approvalOverride.bypassApprovalsAndSandbox
|
|
@@ -360,7 +356,6 @@ function resolveCodexEffectiveRuntimePolicy(input) {
|
|
|
360
356
|
executionMode: input.environment.mode,
|
|
361
357
|
permissionMode: permissionMode ?? null,
|
|
362
358
|
sandbox,
|
|
363
|
-
approvalPolicy,
|
|
364
359
|
fullAuto,
|
|
365
360
|
bypassApprovalsAndSandbox,
|
|
366
361
|
});
|
|
@@ -368,7 +363,6 @@ function resolveCodexEffectiveRuntimePolicy(input) {
|
|
|
368
363
|
...(model ? { model } : {}),
|
|
369
364
|
...(permissionMode ? { permissionMode } : {}),
|
|
370
365
|
sandbox,
|
|
371
|
-
approvalPolicy,
|
|
372
366
|
fullAuto,
|
|
373
367
|
bypassApprovalsAndSandbox,
|
|
374
368
|
fingerprint,
|
|
@@ -426,17 +420,6 @@ function stringArgs(value) {
|
|
|
426
420
|
? value.filter((item) => typeof item === 'string' && item.trim().length > 0)
|
|
427
421
|
: undefined;
|
|
428
422
|
}
|
|
429
|
-
function supportsCodexAppServer(codexBin) {
|
|
430
|
-
if (process.env.CANON_CODEX_TRANSPORT === 'exec')
|
|
431
|
-
return false;
|
|
432
|
-
if (process.env.CANON_CODEX_TRANSPORT === 'app-server')
|
|
433
|
-
return true;
|
|
434
|
-
const result = spawnSync(codexBin, ['app-server', '--help'], {
|
|
435
|
-
encoding: 'utf8',
|
|
436
|
-
stdio: ['ignore', 'ignore', 'ignore'],
|
|
437
|
-
});
|
|
438
|
-
return result.status === 0;
|
|
439
|
-
}
|
|
440
423
|
function parsePlanCommand(content) {
|
|
441
424
|
const trimmed = content.trimStart();
|
|
442
425
|
if (!trimmed.startsWith('/plan'))
|
|
@@ -512,7 +495,6 @@ export async function main() {
|
|
|
512
495
|
model: { type: 'string' },
|
|
513
496
|
sandbox: { type: 'string' },
|
|
514
497
|
'ask-for-approval': { type: 'string' },
|
|
515
|
-
'codex-profile': { type: 'string' },
|
|
516
498
|
'add-dir': { type: 'string', multiple: true },
|
|
517
499
|
workspace: { type: 'string', multiple: true },
|
|
518
500
|
'workspace-root': { type: 'string', multiple: true },
|
|
@@ -545,14 +527,12 @@ export async function main() {
|
|
|
545
527
|
}
|
|
546
528
|
const codexBin = typeof args['codex-bin'] === 'string' ? args['codex-bin'] : 'codex';
|
|
547
529
|
const codexCliStatus = detectCodexCliVersion(codexBin);
|
|
548
|
-
const useAppServer = supportsCodexAppServer(codexBin);
|
|
549
530
|
if (codexCliStatus.version) {
|
|
550
531
|
console.error(`[canon-codex] Detected Codex CLI ${codexCliStatus.version} (${codexBin})`);
|
|
551
532
|
}
|
|
552
533
|
else {
|
|
553
534
|
console.error(`[canon-codex] Could not detect Codex CLI version for ${codexBin}: ${codexCliStatus.error ?? 'unknown result'}`);
|
|
554
535
|
}
|
|
555
|
-
console.error(`[canon-codex] Codex transport: ${useAppServer ? 'app-server' : 'exec --json'}`);
|
|
556
536
|
const { agentName: profileAgentName, profile, lockHandle, } = resolveCanonAgent({ logPrefix: '[canon-codex]', expectedClientType: 'codex' });
|
|
557
537
|
activeLockHandle = lockHandle ?? null;
|
|
558
538
|
console.error(`[canon-codex] Starting${profile ? ` (profile: ${profile})` : ''} in ${workingDir}`);
|
|
@@ -747,35 +727,19 @@ export async function main() {
|
|
|
747
727
|
const initialEffort = config?.effort && CODEX_EFFORT_VALUES.has(config.effort)
|
|
748
728
|
? config.effort
|
|
749
729
|
: null;
|
|
750
|
-
const adapter =
|
|
751
|
-
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
|
|
759
|
-
|
|
760
|
-
|
|
761
|
-
|
|
762
|
-
|
|
763
|
-
dynamicTools: CODEX_APP_DYNAMIC_TOOLS,
|
|
764
|
-
})
|
|
765
|
-
: new CodexConversationAdapter({
|
|
766
|
-
cwd: sessionCwd,
|
|
767
|
-
threadId: storedThreadId,
|
|
768
|
-
codexBin,
|
|
769
|
-
model: policy.model ?? null,
|
|
770
|
-
reasoningEffort: initialEffort,
|
|
771
|
-
sandbox: policy.sandbox,
|
|
772
|
-
approvalPolicy: policy.approvalPolicy,
|
|
773
|
-
codexProfile: typeof args['codex-profile'] === 'string' ? args['codex-profile'] : null,
|
|
774
|
-
addDirs: args['add-dir'] ?? [],
|
|
775
|
-
configOverrides: args.config ?? [],
|
|
776
|
-
fullAuto: policy.fullAuto,
|
|
777
|
-
bypassApprovalsAndSandbox: policy.bypassApprovalsAndSandbox,
|
|
778
|
-
});
|
|
730
|
+
const adapter = new CodexAppServerAdapter({
|
|
731
|
+
cwd: sessionCwd,
|
|
732
|
+
threadId: storedThreadId,
|
|
733
|
+
codexBin,
|
|
734
|
+
model: policy.model ?? null,
|
|
735
|
+
reasoningEffort: initialEffort,
|
|
736
|
+
sandbox: policy.sandbox,
|
|
737
|
+
addDirs: args['add-dir'] ?? [],
|
|
738
|
+
configOverrides: args.config ?? [],
|
|
739
|
+
fullAuto: policy.fullAuto,
|
|
740
|
+
bypassApprovalsAndSandbox: policy.bypassApprovalsAndSandbox,
|
|
741
|
+
dynamicTools: CODEX_APP_DYNAMIC_TOOLS,
|
|
742
|
+
});
|
|
779
743
|
// eslint-disable-next-line prefer-const -- session must be declared before the module closures but assigned after them
|
|
780
744
|
let session;
|
|
781
745
|
// ── Per-session RTDB writers (generic skeleton from @canonmsg/agent-host) ──
|
|
@@ -1338,9 +1302,6 @@ export async function main() {
|
|
|
1338
1302
|
const params = request.params;
|
|
1339
1303
|
const expiresAt = Date.now() + 30 * 60_000;
|
|
1340
1304
|
if (request.method === 'item/tool/call' && isCodexAppToolCall(params)) {
|
|
1341
|
-
if (!(session.adapter instanceof CodexAppServerAdapter)) {
|
|
1342
|
-
return deniedCodexAppToolResult('codex_app tools require the Codex app-server transport.');
|
|
1343
|
-
}
|
|
1344
1305
|
if (!session.currentTurnCanUseCodexAppTools) {
|
|
1345
1306
|
return deniedCodexAppToolResult('Only the Canon owner can use codex_app tools.');
|
|
1346
1307
|
}
|
|
@@ -1571,11 +1532,9 @@ export async function main() {
|
|
|
1571
1532
|
const renderedContent = renderInboundContent(input.message, materialized);
|
|
1572
1533
|
const turnMetadata = normalizeTurnMetadata(input.message.metadata);
|
|
1573
1534
|
const requestedPlanMode = turnMetadata?.requestedTurnMode === 'plan';
|
|
1574
|
-
const planCommand =
|
|
1575
|
-
?
|
|
1576
|
-
|
|
1577
|
-
: parsePlanCommand(renderedContent)
|
|
1578
|
-
: { planMode: false, content: renderedContent };
|
|
1535
|
+
const planCommand = requestedPlanMode
|
|
1536
|
+
? { planMode: true, content: renderedContent }
|
|
1537
|
+
: parsePlanCommand(renderedContent);
|
|
1579
1538
|
const content = planCommand.content;
|
|
1580
1539
|
const hydrated = await loadHydratedInboundContext({
|
|
1581
1540
|
conversationId: input.conversationId,
|
|
@@ -1750,16 +1709,14 @@ export async function main() {
|
|
|
1750
1709
|
permissionModes: [...codexPermissionEnvelope.availablePermissionModes],
|
|
1751
1710
|
defaultPermissionMode: codexPermissionEnvelope.defaultPermissionMode,
|
|
1752
1711
|
presentation: runtimePresentation,
|
|
1753
|
-
supportsPlanMode:
|
|
1754
|
-
supportsCompact:
|
|
1755
|
-
supportsRichCards:
|
|
1712
|
+
supportsPlanMode: true,
|
|
1713
|
+
supportsCompact: true,
|
|
1714
|
+
supportsRichCards: true,
|
|
1756
1715
|
skills: codexSkills,
|
|
1757
1716
|
}),
|
|
1758
1717
|
});
|
|
1759
1718
|
let runtimeDescriptor = buildCurrentRuntimeDescriptor();
|
|
1760
1719
|
async function refreshCodexSkillInventory(forceReload = false) {
|
|
1761
|
-
if (!useAppServer)
|
|
1762
|
-
return;
|
|
1763
1720
|
const probe = new CodexAppServerAdapter({
|
|
1764
1721
|
cwd: workingDir,
|
|
1765
1722
|
codexBin,
|
|
@@ -1844,10 +1801,6 @@ export async function main() {
|
|
|
1844
1801
|
console.error(`[canon-codex] [${conversationId.slice(0, 8)}] Cannot compact context: no live Codex session`);
|
|
1845
1802
|
return;
|
|
1846
1803
|
}
|
|
1847
|
-
if (!(session.adapter instanceof CodexAppServerAdapter)) {
|
|
1848
|
-
console.error(`[canon-codex] [${conversationId.slice(0, 8)}] Cannot compact context: compact is only available for Codex app-server sessions`);
|
|
1849
|
-
return;
|
|
1850
|
-
}
|
|
1851
1804
|
try {
|
|
1852
1805
|
await session.adapter.compactThread();
|
|
1853
1806
|
console.error(`[canon-codex] [${conversationId.slice(0, 8)}] Compact requested`);
|
|
@@ -1930,24 +1883,16 @@ export async function main() {
|
|
|
1930
1883
|
const payload = {
|
|
1931
1884
|
descriptor,
|
|
1932
1885
|
surfaceMode: 'host',
|
|
1933
|
-
// The exec --json transport cannot block on approvals — without a
|
|
1934
|
-
// strip-level warning a user can believe they have an approval
|
|
1935
|
-
// gate they do not have.
|
|
1936
|
-
...(useAppServer
|
|
1937
|
-
? {}
|
|
1938
|
-
: { warning: "Approvals can't block on this Codex CLI — update Codex to enable the app-server transport and approval gates." }),
|
|
1939
1886
|
statusItems: [
|
|
1940
1887
|
{
|
|
1941
1888
|
id: 'transport',
|
|
1942
1889
|
label: 'Transport',
|
|
1943
|
-
value:
|
|
1890
|
+
value: 'app-server',
|
|
1944
1891
|
},
|
|
1945
1892
|
{
|
|
1946
1893
|
id: 'streaming',
|
|
1947
1894
|
label: 'Live output',
|
|
1948
|
-
value:
|
|
1949
|
-
? 'Plans, questions, approvals, tools, and message deltas'
|
|
1950
|
-
: 'Thinking, tools, and completed-message previews',
|
|
1895
|
+
value: 'Plans, questions, approvals, tools, and message deltas',
|
|
1951
1896
|
},
|
|
1952
1897
|
{
|
|
1953
1898
|
id: 'codex-cli',
|
|
@@ -1958,8 +1903,7 @@ export async function main() {
|
|
|
1958
1903
|
{
|
|
1959
1904
|
id: 'nativeActions',
|
|
1960
1905
|
label: 'Native actions',
|
|
1961
|
-
value:
|
|
1962
|
-
...(useAppServer ? {} : { tone: 'warning' }),
|
|
1906
|
+
value: 'Enabled',
|
|
1963
1907
|
},
|
|
1964
1908
|
{
|
|
1965
1909
|
id: 'mediaOut',
|
|
@@ -1978,9 +1922,7 @@ export async function main() {
|
|
|
1978
1922
|
fallbackReason: resolveExecutionFallbackReason(session?.environment),
|
|
1979
1923
|
},
|
|
1980
1924
|
notes: [
|
|
1981
|
-
|
|
1982
|
-
? 'This Codex host uses the app-server transport, so Canon can route native plan mode, runtime questions, approvals, and live turn updates.'
|
|
1983
|
-
: 'This Codex host uses the current exec --json transport, so Canon can show thinking, tool activity, and completed assistant-message previews, but not native plan questions or structured approvals.',
|
|
1925
|
+
'This Codex host uses the app-server transport, so Canon can route native plan mode, runtime questions, approvals, and live turn updates.',
|
|
1984
1926
|
],
|
|
1985
1927
|
};
|
|
1986
1928
|
await port.publishRuntimeStatus({
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@canonmsg/codex-plugin",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.20.0",
|
|
4
4
|
"description": "Canon host integration for Codex CLI",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/index.js",
|
|
@@ -29,12 +29,12 @@
|
|
|
29
29
|
"prepack": "npm run build"
|
|
30
30
|
},
|
|
31
31
|
"dependencies": {
|
|
32
|
-
"@canonmsg/agent-host": "^0.
|
|
33
|
-
"@canonmsg/agent-sdk": "^
|
|
34
|
-
"@canonmsg/backend-contracts": "^1.
|
|
35
|
-
"@canonmsg/bridge": "^0.
|
|
36
|
-
"@canonmsg/core": "^3.
|
|
37
|
-
"@canonmsg/framework": "^0.
|
|
32
|
+
"@canonmsg/agent-host": "^0.5.0",
|
|
33
|
+
"@canonmsg/agent-sdk": "^4.0.0",
|
|
34
|
+
"@canonmsg/backend-contracts": "^1.9.0",
|
|
35
|
+
"@canonmsg/bridge": "^0.3.0",
|
|
36
|
+
"@canonmsg/core": "^3.3.0",
|
|
37
|
+
"@canonmsg/framework": "^0.3.0"
|
|
38
38
|
},
|
|
39
39
|
"engines": {
|
|
40
40
|
"node": ">=18.0.0"
|
package/dist/adapter.d.ts
DELETED
|
@@ -1,97 +0,0 @@
|
|
|
1
|
-
export type CodexSandboxMode = 'read-only' | 'workspace-write' | 'danger-full-access';
|
|
2
|
-
export type CodexApprovalPolicy = 'untrusted' | 'on-request' | 'never';
|
|
3
|
-
export type CodexEvent = {
|
|
4
|
-
type: 'thread.started';
|
|
5
|
-
threadId: string;
|
|
6
|
-
} | {
|
|
7
|
-
type: 'turn.started';
|
|
8
|
-
} | {
|
|
9
|
-
type: 'message';
|
|
10
|
-
text: string;
|
|
11
|
-
delta?: string;
|
|
12
|
-
itemId?: string;
|
|
13
|
-
} | {
|
|
14
|
-
type: 'plan.updated';
|
|
15
|
-
text: string;
|
|
16
|
-
} | {
|
|
17
|
-
type: 'waiting';
|
|
18
|
-
reason: string;
|
|
19
|
-
} | {
|
|
20
|
-
type: 'command.started';
|
|
21
|
-
command: string;
|
|
22
|
-
itemId?: string;
|
|
23
|
-
} | {
|
|
24
|
-
type: 'command.completed';
|
|
25
|
-
command: string;
|
|
26
|
-
output: string;
|
|
27
|
-
exitCode: number | null;
|
|
28
|
-
itemId?: string;
|
|
29
|
-
} | {
|
|
30
|
-
type: 'turn.completed';
|
|
31
|
-
usage?: {
|
|
32
|
-
input_tokens?: number;
|
|
33
|
-
cached_input_tokens?: number;
|
|
34
|
-
output_tokens?: number;
|
|
35
|
-
};
|
|
36
|
-
} | {
|
|
37
|
-
type: 'skills.changed';
|
|
38
|
-
};
|
|
39
|
-
export interface CodexServerRequest {
|
|
40
|
-
id: string | number;
|
|
41
|
-
method: string;
|
|
42
|
-
params: Record<string, unknown>;
|
|
43
|
-
}
|
|
44
|
-
export interface CodexRunTurnOptions {
|
|
45
|
-
planMode?: boolean;
|
|
46
|
-
onServerRequest?: (request: CodexServerRequest) => Promise<unknown>;
|
|
47
|
-
}
|
|
48
|
-
export interface CodexTurnResult {
|
|
49
|
-
threadId: string | null;
|
|
50
|
-
finalMessage: string | null;
|
|
51
|
-
exitCode: number | null;
|
|
52
|
-
interrupted: boolean;
|
|
53
|
-
errorText: string | null;
|
|
54
|
-
}
|
|
55
|
-
export declare class CodexConversationAdapter {
|
|
56
|
-
private readonly cwd;
|
|
57
|
-
private readonly codexBin;
|
|
58
|
-
private model;
|
|
59
|
-
private reasoningEffort;
|
|
60
|
-
private readonly sandbox;
|
|
61
|
-
private readonly legacyApprovalPolicy;
|
|
62
|
-
private readonly codexProfile;
|
|
63
|
-
private readonly addDirs;
|
|
64
|
-
private readonly configOverrides;
|
|
65
|
-
private readonly fullAuto;
|
|
66
|
-
private readonly bypassApprovalsAndSandbox;
|
|
67
|
-
private child;
|
|
68
|
-
private threadId;
|
|
69
|
-
private interruptTimer;
|
|
70
|
-
private interrupted;
|
|
71
|
-
constructor(opts: {
|
|
72
|
-
cwd: string;
|
|
73
|
-
threadId?: string | null;
|
|
74
|
-
codexBin?: string;
|
|
75
|
-
model?: string | null;
|
|
76
|
-
reasoningEffort?: string | null;
|
|
77
|
-
sandbox?: CodexSandboxMode | null;
|
|
78
|
-
approvalPolicy?: CodexApprovalPolicy | null;
|
|
79
|
-
codexProfile?: string | null;
|
|
80
|
-
addDirs?: string[];
|
|
81
|
-
configOverrides?: string[];
|
|
82
|
-
fullAuto?: boolean;
|
|
83
|
-
bypassApprovalsAndSandbox?: boolean;
|
|
84
|
-
});
|
|
85
|
-
getThreadId(): string | null;
|
|
86
|
-
clearThreadId(): void;
|
|
87
|
-
setModel(model: string | null): void;
|
|
88
|
-
/** Sets GPT reasoning effort, applied on the next turn via `-c model_reasoning_effort`. */
|
|
89
|
-
setReasoningEffort(effort: string | null): void;
|
|
90
|
-
isRunning(): boolean;
|
|
91
|
-
interrupt(): Promise<void>;
|
|
92
|
-
runTurn(prompt: string, onEvent: (event: CodexEvent) => void, onLog?: (line: string) => void, imagePaths?: readonly string[], extraAddDirs?: readonly string[], _options?: CodexRunTurnOptions): Promise<CodexTurnResult>;
|
|
93
|
-
private buildAddDirs;
|
|
94
|
-
private buildArgs;
|
|
95
|
-
private canResumeWithCurrentPolicy;
|
|
96
|
-
private clearActiveProcess;
|
|
97
|
-
}
|
package/dist/adapter.js
DELETED
|
@@ -1,310 +0,0 @@
|
|
|
1
|
-
import { spawn } from 'node:child_process';
|
|
2
|
-
import { createInterface } from 'node:readline';
|
|
3
|
-
import { isRecoverableCodexThreadError } from './error-format.js';
|
|
4
|
-
export class CodexConversationAdapter {
|
|
5
|
-
cwd;
|
|
6
|
-
codexBin;
|
|
7
|
-
model;
|
|
8
|
-
reasoningEffort;
|
|
9
|
-
sandbox;
|
|
10
|
-
legacyApprovalPolicy;
|
|
11
|
-
codexProfile;
|
|
12
|
-
addDirs;
|
|
13
|
-
configOverrides;
|
|
14
|
-
fullAuto;
|
|
15
|
-
bypassApprovalsAndSandbox;
|
|
16
|
-
child = null;
|
|
17
|
-
threadId;
|
|
18
|
-
interruptTimer = null;
|
|
19
|
-
interrupted = false;
|
|
20
|
-
constructor(opts) {
|
|
21
|
-
this.cwd = opts.cwd;
|
|
22
|
-
this.threadId = opts.threadId ?? null;
|
|
23
|
-
this.codexBin = opts.codexBin ?? 'codex';
|
|
24
|
-
this.model = opts.model ?? null;
|
|
25
|
-
this.reasoningEffort = opts.reasoningEffort ?? null;
|
|
26
|
-
this.sandbox = opts.sandbox ?? null;
|
|
27
|
-
this.legacyApprovalPolicy = opts.approvalPolicy ?? null;
|
|
28
|
-
this.codexProfile = opts.codexProfile ?? null;
|
|
29
|
-
this.addDirs = opts.addDirs ?? [];
|
|
30
|
-
this.configOverrides = opts.configOverrides ?? [];
|
|
31
|
-
this.fullAuto = opts.fullAuto ?? false;
|
|
32
|
-
this.bypassApprovalsAndSandbox = opts.bypassApprovalsAndSandbox ?? false;
|
|
33
|
-
}
|
|
34
|
-
getThreadId() {
|
|
35
|
-
return this.threadId;
|
|
36
|
-
}
|
|
37
|
-
clearThreadId() {
|
|
38
|
-
this.threadId = null;
|
|
39
|
-
}
|
|
40
|
-
setModel(model) {
|
|
41
|
-
this.model = model;
|
|
42
|
-
}
|
|
43
|
-
/** Sets GPT reasoning effort, applied on the next turn via `-c model_reasoning_effort`. */
|
|
44
|
-
setReasoningEffort(effort) {
|
|
45
|
-
this.reasoningEffort = effort && effort.trim() ? effort.trim() : null;
|
|
46
|
-
}
|
|
47
|
-
isRunning() {
|
|
48
|
-
return this.child !== null;
|
|
49
|
-
}
|
|
50
|
-
async interrupt() {
|
|
51
|
-
if (!this.child)
|
|
52
|
-
return;
|
|
53
|
-
this.interrupted = true;
|
|
54
|
-
this.child.kill('SIGINT');
|
|
55
|
-
this.interruptTimer = setTimeout(() => {
|
|
56
|
-
if (this.child)
|
|
57
|
-
this.child.kill('SIGKILL');
|
|
58
|
-
}, 5_000);
|
|
59
|
-
}
|
|
60
|
-
async runTurn(prompt, onEvent, onLog, imagePaths = [], extraAddDirs = [], _options = {}) {
|
|
61
|
-
if (this.child) {
|
|
62
|
-
throw new Error('A Codex turn is already in progress for this conversation');
|
|
63
|
-
}
|
|
64
|
-
const args = this.buildArgs(prompt, imagePaths, extraAddDirs);
|
|
65
|
-
const child = spawn(this.codexBin, args, {
|
|
66
|
-
cwd: this.cwd,
|
|
67
|
-
stdio: ['ignore', 'pipe', 'pipe'],
|
|
68
|
-
});
|
|
69
|
-
this.child = child;
|
|
70
|
-
this.interrupted = false;
|
|
71
|
-
let latestMessage = null;
|
|
72
|
-
let lastErrorText = null;
|
|
73
|
-
const stdout = createInterface({ input: child.stdout });
|
|
74
|
-
const stderr = createInterface({ input: child.stderr });
|
|
75
|
-
stdout.on('line', (line) => {
|
|
76
|
-
const event = parseEventLine(line);
|
|
77
|
-
if (!event)
|
|
78
|
-
return;
|
|
79
|
-
switch (event.type) {
|
|
80
|
-
case 'thread.started':
|
|
81
|
-
this.threadId = event.thread_id;
|
|
82
|
-
onEvent({ type: 'thread.started', threadId: event.thread_id });
|
|
83
|
-
break;
|
|
84
|
-
case 'turn.started':
|
|
85
|
-
onEvent({ type: 'turn.started' });
|
|
86
|
-
break;
|
|
87
|
-
case 'item.started':
|
|
88
|
-
if (event.item?.type === 'command_execution') {
|
|
89
|
-
onEvent({
|
|
90
|
-
type: 'command.started',
|
|
91
|
-
command: String(event.item.command ?? ''),
|
|
92
|
-
...(typeof event.item.id === 'string' && event.item.id.trim()
|
|
93
|
-
? { itemId: event.item.id.trim() }
|
|
94
|
-
: {}),
|
|
95
|
-
});
|
|
96
|
-
}
|
|
97
|
-
break;
|
|
98
|
-
case 'item.completed':
|
|
99
|
-
if (event.item?.type === 'agent_message') {
|
|
100
|
-
latestMessage = normalizeMessageText(event.item.text);
|
|
101
|
-
if (latestMessage) {
|
|
102
|
-
onEvent({
|
|
103
|
-
type: 'message',
|
|
104
|
-
text: latestMessage,
|
|
105
|
-
...(typeof event.item.id === 'string' && event.item.id.trim()
|
|
106
|
-
? { itemId: event.item.id.trim() }
|
|
107
|
-
: {}),
|
|
108
|
-
});
|
|
109
|
-
}
|
|
110
|
-
}
|
|
111
|
-
else if (event.item?.type === 'command_execution') {
|
|
112
|
-
onEvent({
|
|
113
|
-
type: 'command.completed',
|
|
114
|
-
command: String(event.item.command ?? ''),
|
|
115
|
-
output: String(event.item.aggregated_output ?? ''),
|
|
116
|
-
exitCode: typeof event.item.exit_code === 'number' ? event.item.exit_code : null,
|
|
117
|
-
...(typeof event.item.id === 'string' && event.item.id.trim()
|
|
118
|
-
? { itemId: event.item.id.trim() }
|
|
119
|
-
: {}),
|
|
120
|
-
});
|
|
121
|
-
}
|
|
122
|
-
break;
|
|
123
|
-
case 'turn.completed':
|
|
124
|
-
onEvent({ type: 'turn.completed', usage: event.usage });
|
|
125
|
-
break;
|
|
126
|
-
case 'error':
|
|
127
|
-
lastErrorText = normalizeErrorText(event.message) ?? lastErrorText;
|
|
128
|
-
break;
|
|
129
|
-
case 'turn.failed':
|
|
130
|
-
lastErrorText = normalizeErrorText(event.error?.message ?? event.message) ?? lastErrorText;
|
|
131
|
-
break;
|
|
132
|
-
default:
|
|
133
|
-
break;
|
|
134
|
-
}
|
|
135
|
-
});
|
|
136
|
-
stderr.on('line', (line) => {
|
|
137
|
-
const trimmed = line.trim();
|
|
138
|
-
if (!trimmed)
|
|
139
|
-
return;
|
|
140
|
-
if (isIgnorableCodexLog(trimmed))
|
|
141
|
-
return;
|
|
142
|
-
lastErrorText = trimmed;
|
|
143
|
-
if (isRecoverableCodexThreadError(trimmed))
|
|
144
|
-
return;
|
|
145
|
-
onLog?.(trimmed);
|
|
146
|
-
});
|
|
147
|
-
return await new Promise((resolve, reject) => {
|
|
148
|
-
child.on('error', (error) => {
|
|
149
|
-
this.clearActiveProcess();
|
|
150
|
-
reject(error);
|
|
151
|
-
});
|
|
152
|
-
child.on('close', (code) => {
|
|
153
|
-
stdout.close();
|
|
154
|
-
stderr.close();
|
|
155
|
-
const interrupted = this.interrupted || code === 130;
|
|
156
|
-
const result = {
|
|
157
|
-
threadId: this.threadId,
|
|
158
|
-
finalMessage: latestMessage,
|
|
159
|
-
exitCode: code,
|
|
160
|
-
interrupted,
|
|
161
|
-
errorText: interrupted ? null : lastErrorText,
|
|
162
|
-
};
|
|
163
|
-
this.clearActiveProcess();
|
|
164
|
-
resolve(result);
|
|
165
|
-
});
|
|
166
|
-
});
|
|
167
|
-
}
|
|
168
|
-
buildAddDirs(extraAddDirs = []) {
|
|
169
|
-
const seen = new Set();
|
|
170
|
-
const dirs = [];
|
|
171
|
-
for (const value of [...this.addDirs, ...extraAddDirs]) {
|
|
172
|
-
const trimmed = value.trim();
|
|
173
|
-
if (!trimmed || seen.has(trimmed))
|
|
174
|
-
continue;
|
|
175
|
-
seen.add(trimmed);
|
|
176
|
-
dirs.push(value);
|
|
177
|
-
}
|
|
178
|
-
return dirs;
|
|
179
|
-
}
|
|
180
|
-
buildArgs(prompt, imagePaths = [], extraAddDirs = []) {
|
|
181
|
-
if (this.threadId && this.canResumeWithCurrentPolicy()) {
|
|
182
|
-
const args = ['exec', 'resume', '--json', '--skip-git-repo-check'];
|
|
183
|
-
if (this.model) {
|
|
184
|
-
args.push('-m', this.model);
|
|
185
|
-
}
|
|
186
|
-
if (this.codexProfile) {
|
|
187
|
-
args.push('-p', this.codexProfile);
|
|
188
|
-
}
|
|
189
|
-
for (const configOverride of this.configOverrides) {
|
|
190
|
-
args.push('-c', configOverride);
|
|
191
|
-
}
|
|
192
|
-
if (this.reasoningEffort) {
|
|
193
|
-
args.push('-c', `model_reasoning_effort="${this.reasoningEffort}"`);
|
|
194
|
-
}
|
|
195
|
-
for (const addDir of this.buildAddDirs(extraAddDirs)) {
|
|
196
|
-
args.push('--add-dir', addDir);
|
|
197
|
-
}
|
|
198
|
-
if (this.fullAuto) {
|
|
199
|
-
args.push('--full-auto');
|
|
200
|
-
}
|
|
201
|
-
if (this.bypassApprovalsAndSandbox) {
|
|
202
|
-
args.push('--dangerously-bypass-approvals-and-sandbox');
|
|
203
|
-
}
|
|
204
|
-
for (const imagePath of imagePaths) {
|
|
205
|
-
args.push('-i', imagePath);
|
|
206
|
-
}
|
|
207
|
-
if (imagePaths.length > 0) {
|
|
208
|
-
args.push('--');
|
|
209
|
-
}
|
|
210
|
-
args.push(this.threadId, prompt);
|
|
211
|
-
return args;
|
|
212
|
-
}
|
|
213
|
-
if (this.threadId) {
|
|
214
|
-
this.threadId = null;
|
|
215
|
-
}
|
|
216
|
-
const args = ['exec', '--json', '--color', 'never', '-C', this.cwd, '--skip-git-repo-check'];
|
|
217
|
-
const execMode = resolveExecMode({
|
|
218
|
-
sandbox: this.sandbox,
|
|
219
|
-
fullAuto: this.fullAuto,
|
|
220
|
-
bypassApprovalsAndSandbox: this.bypassApprovalsAndSandbox,
|
|
221
|
-
});
|
|
222
|
-
if (this.model) {
|
|
223
|
-
args.push('-m', this.model);
|
|
224
|
-
}
|
|
225
|
-
if (this.sandbox) {
|
|
226
|
-
args.push('-s', this.sandbox);
|
|
227
|
-
}
|
|
228
|
-
if (this.codexProfile) {
|
|
229
|
-
args.push('-p', this.codexProfile);
|
|
230
|
-
}
|
|
231
|
-
for (const addDir of this.buildAddDirs(extraAddDirs)) {
|
|
232
|
-
args.push('--add-dir', addDir);
|
|
233
|
-
}
|
|
234
|
-
for (const configOverride of this.configOverrides) {
|
|
235
|
-
args.push('-c', configOverride);
|
|
236
|
-
}
|
|
237
|
-
if (this.reasoningEffort) {
|
|
238
|
-
args.push('-c', `model_reasoning_effort="${this.reasoningEffort}"`);
|
|
239
|
-
}
|
|
240
|
-
if (execMode.fullAuto) {
|
|
241
|
-
args.push('--full-auto');
|
|
242
|
-
}
|
|
243
|
-
if (execMode.bypassApprovalsAndSandbox) {
|
|
244
|
-
args.push('--dangerously-bypass-approvals-and-sandbox');
|
|
245
|
-
}
|
|
246
|
-
for (const imagePath of imagePaths) {
|
|
247
|
-
args.push('-i', imagePath);
|
|
248
|
-
}
|
|
249
|
-
if (imagePaths.length > 0) {
|
|
250
|
-
args.push('--');
|
|
251
|
-
}
|
|
252
|
-
args.push(prompt);
|
|
253
|
-
return args;
|
|
254
|
-
}
|
|
255
|
-
canResumeWithCurrentPolicy() {
|
|
256
|
-
if (this.bypassApprovalsAndSandbox || this.fullAuto) {
|
|
257
|
-
return true;
|
|
258
|
-
}
|
|
259
|
-
return this.sandbox === null;
|
|
260
|
-
}
|
|
261
|
-
clearActiveProcess() {
|
|
262
|
-
if (this.interruptTimer) {
|
|
263
|
-
clearTimeout(this.interruptTimer);
|
|
264
|
-
this.interruptTimer = null;
|
|
265
|
-
}
|
|
266
|
-
this.child = null;
|
|
267
|
-
}
|
|
268
|
-
}
|
|
269
|
-
function parseEventLine(line) {
|
|
270
|
-
const trimmed = line.trim();
|
|
271
|
-
if (!trimmed.startsWith('{'))
|
|
272
|
-
return null;
|
|
273
|
-
try {
|
|
274
|
-
return JSON.parse(trimmed);
|
|
275
|
-
}
|
|
276
|
-
catch {
|
|
277
|
-
return null;
|
|
278
|
-
}
|
|
279
|
-
}
|
|
280
|
-
function normalizeMessageText(value) {
|
|
281
|
-
if (typeof value !== 'string')
|
|
282
|
-
return null;
|
|
283
|
-
const text = value.trim();
|
|
284
|
-
return text ? text : null;
|
|
285
|
-
}
|
|
286
|
-
function normalizeErrorText(value) {
|
|
287
|
-
if (typeof value !== 'string')
|
|
288
|
-
return null;
|
|
289
|
-
const text = value.trim();
|
|
290
|
-
return text ? text : null;
|
|
291
|
-
}
|
|
292
|
-
function resolveExecMode(input) {
|
|
293
|
-
if (input.bypassApprovalsAndSandbox) {
|
|
294
|
-
return { fullAuto: false, bypassApprovalsAndSandbox: true };
|
|
295
|
-
}
|
|
296
|
-
if (input.fullAuto) {
|
|
297
|
-
return { fullAuto: true, bypassApprovalsAndSandbox: false };
|
|
298
|
-
}
|
|
299
|
-
return { fullAuto: false, bypassApprovalsAndSandbox: false };
|
|
300
|
-
}
|
|
301
|
-
function isIgnorableCodexLog(line) {
|
|
302
|
-
return [
|
|
303
|
-
'Reading additional input from stdin...',
|
|
304
|
-
'ignoring interface.defaultPrompt',
|
|
305
|
-
'state db discrepancy during find_thread_path_by_id_str_in_subdir',
|
|
306
|
-
'failed to open state db',
|
|
307
|
-
'failed to initialize state runtime',
|
|
308
|
-
'Failed to delete shell snapshot',
|
|
309
|
-
].some((pattern) => line.includes(pattern));
|
|
310
|
-
}
|