@canonmsg/codex-plugin 0.6.1 → 0.6.3
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/cli-entry.d.ts +2 -0
- package/dist/cli-entry.js +26 -0
- package/dist/host.d.ts +1 -1
- package/dist/host.js +14 -23
- package/dist/index.d.ts +3 -0
- package/dist/index.js +3 -0
- package/dist/register.d.ts +1 -1
- package/dist/register.js +78 -71
- package/dist/setup.d.ts +1 -1
- package/dist/setup.js +36 -29
- package/package.json +10 -4
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { realpathSync } from 'node:fs';
|
|
2
|
+
import { resolve } from 'node:path';
|
|
3
|
+
import { pathToFileURL } from 'node:url';
|
|
4
|
+
export function isDirectExecution(moduleUrl) {
|
|
5
|
+
const entry = process.argv[1];
|
|
6
|
+
if (!entry)
|
|
7
|
+
return false;
|
|
8
|
+
const abs = resolve(entry);
|
|
9
|
+
let real = abs;
|
|
10
|
+
try {
|
|
11
|
+
real = realpathSync(abs);
|
|
12
|
+
}
|
|
13
|
+
catch {
|
|
14
|
+
// argv[1] may not exist on disk in rare harnesses; fall back to abs
|
|
15
|
+
}
|
|
16
|
+
return (pathToFileURL(real).href === moduleUrl ||
|
|
17
|
+
pathToFileURL(abs).href === moduleUrl);
|
|
18
|
+
}
|
|
19
|
+
export function runCli(moduleUrl, main, onError) {
|
|
20
|
+
if (!isDirectExecution(moduleUrl)) {
|
|
21
|
+
return;
|
|
22
|
+
}
|
|
23
|
+
Promise.resolve()
|
|
24
|
+
.then(() => main())
|
|
25
|
+
.catch(onError);
|
|
26
|
+
}
|
package/dist/host.d.ts
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
export
|
|
2
|
+
export declare function main(): Promise<void>;
|
package/dist/host.js
CHANGED
|
@@ -1,15 +1,15 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import { setDefaultResultOrder } from 'node:dns';
|
|
3
|
-
setDefaultResultOrder('ipv4first');
|
|
4
3
|
import { randomUUID } from 'node:crypto';
|
|
5
4
|
import { parseArgs } from 'node:util';
|
|
6
5
|
import { getCodexImagePath, materializeMessageMedia, } from '@canonmsg/agent-sdk';
|
|
7
|
-
import { buildConfiguredWorkspaceOptions, buildPublicWorkspaceOptions, EXECUTION_ENVIRONMENT_MODES, ExecutionEnvironmentError,
|
|
6
|
+
import { buildConfiguredWorkspaceOptions, buildPublicWorkspaceOptions, EXECUTION_ENVIRONMENT_MODES, ExecutionEnvironmentError, CanonClient, CanonStream, clearSessionState, clearTurnState, DEFAULT_PARTICIPATION_HISTORY_FETCH_LIMIT, DEFAULT_RUNTIME_CAPABILITIES, FINAL_MESSAGE_HANDOFF_MS, getActiveProfile, initRTDBAuth, normalizeTurnMetadata, normalizeTurnState, prepareConversationEnvironment, releaseLock, releaseConversationEnvironment, resolveCanonAgent, rtdbRead, rtdbWrite, shouldTriggerAgentTurn, writeSessionState, writeTurnState, } from '@canonmsg/core';
|
|
8
7
|
import { buildCanonHostPrompt, buildHydratedInboundContext, createConversationMetadataLoader, loadHostSessionConfig, publishHostAgentRuntime, renderCanonHostInboundContent, resolveHostWorkspaceCwd, } from './host-runtime.js';
|
|
9
8
|
import { buildInboundContextLines, decideAutoReply, } from './inbound-policy.js';
|
|
10
9
|
import { CodexConversationAdapter, } from './adapter.js';
|
|
11
10
|
import { clearStoredThreadId, loadStoredThreadId, saveStoredThreadId, } from './session-store.js';
|
|
12
11
|
import { deriveCodexPermissionEnvelope, mapCanonPermissionToCodex, } from './permission-mode.js';
|
|
12
|
+
import { runCli } from './cli-entry.js';
|
|
13
13
|
const MAX_SESSIONS = 12;
|
|
14
14
|
const IDLE_TIMEOUT_MS = 30 * 60 * 1000;
|
|
15
15
|
const HEARTBEAT_MS = 30_000;
|
|
@@ -49,13 +49,11 @@ async function loadSessionConfig(conversationId, agentId) {
|
|
|
49
49
|
extraStringFields: ['permissionMode'],
|
|
50
50
|
});
|
|
51
51
|
}
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
}
|
|
58
|
-
return mode;
|
|
52
|
+
// Default to 'locked' (shared workspace) when no mode has been picked. The
|
|
53
|
+
// UI still lets owners flip to 'worktree'; this just stops sessions from
|
|
54
|
+
// failing closed when the mode has never been written.
|
|
55
|
+
function resolveSessionExecutionMode(config) {
|
|
56
|
+
return config?.executionMode ?? 'locked';
|
|
59
57
|
}
|
|
60
58
|
function resolveWorkspaceCwd(config) {
|
|
61
59
|
return resolveHostWorkspaceCwd({
|
|
@@ -92,7 +90,8 @@ function formatTurnFailure(errorText) {
|
|
|
92
90
|
function sleep(ms) {
|
|
93
91
|
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
94
92
|
}
|
|
95
|
-
async function main() {
|
|
93
|
+
export async function main() {
|
|
94
|
+
setDefaultResultOrder('ipv4first');
|
|
96
95
|
const { values: args } = parseArgs({
|
|
97
96
|
options: {
|
|
98
97
|
cwd: { type: 'string' },
|
|
@@ -105,17 +104,12 @@ async function main() {
|
|
|
105
104
|
config: { type: 'string', multiple: true },
|
|
106
105
|
'codex-bin': { type: 'string' },
|
|
107
106
|
'full-auto': { type: 'boolean' },
|
|
108
|
-
'enable-worktrees': { type: 'boolean' },
|
|
109
107
|
'dangerously-bypass-approvals-and-sandbox': { type: 'boolean' },
|
|
110
108
|
},
|
|
111
109
|
strict: true,
|
|
112
110
|
});
|
|
113
111
|
workingDir = (typeof args.cwd === 'string' ? args.cwd : null) || process.cwd();
|
|
114
112
|
workspaceOptions = buildConfiguredWorkspaceOptions(workingDir, args.workspace ?? []);
|
|
115
|
-
const allowWorktrees = isEnabledFlag(args['enable-worktrees'] ?? process.env.CANON_ENABLE_WORKTREES);
|
|
116
|
-
if (!allowWorktrees) {
|
|
117
|
-
console.error('[canon-codex] Worktree isolation is disabled; sessions will lock their selected workspace unless explicitly enabled.');
|
|
118
|
-
}
|
|
119
113
|
if (typeof args['ask-for-approval'] === 'string') {
|
|
120
114
|
console.error('[canon-codex] Note: newer Codex CLI releases do not accept --ask-for-approval for `codex exec`; Canon will translate compatible legacy usage when possible.');
|
|
121
115
|
}
|
|
@@ -255,10 +249,7 @@ async function main() {
|
|
|
255
249
|
}
|
|
256
250
|
const creation = (async () => {
|
|
257
251
|
const config = await loadSessionConfig(conversationId, agentId);
|
|
258
|
-
const sessionExecutionMode =
|
|
259
|
-
if (sessionExecutionMode === 'worktree' && !allowWorktrees) {
|
|
260
|
-
throw new ExecutionEnvironmentError('This host does not allow worktree sessions (launched without --enable-worktrees).', 'This Canon host was started without worktree isolation enabled. Choose "Lock the workspace" or restart the host with --enable-worktrees.');
|
|
261
|
-
}
|
|
252
|
+
const sessionExecutionMode = resolveSessionExecutionMode(config);
|
|
262
253
|
const workspaceCwd = resolveWorkspaceCwd(config);
|
|
263
254
|
const environment = prepareConversationEnvironment({
|
|
264
255
|
agentId,
|
|
@@ -547,9 +538,9 @@ async function main() {
|
|
|
547
538
|
}
|
|
548
539
|
let controlStopped = false;
|
|
549
540
|
let streamConnected = false;
|
|
550
|
-
const hostAvailableExecutionModes =
|
|
551
|
-
|
|
552
|
-
|
|
541
|
+
const hostAvailableExecutionModes = [
|
|
542
|
+
...EXECUTION_ENVIRONMENT_MODES,
|
|
543
|
+
];
|
|
553
544
|
const codexPermissionEnvelope = deriveCodexPermissionEnvelope(args);
|
|
554
545
|
let runtimeDescriptor = {
|
|
555
546
|
defaultWorkspaceId: workspaceOptions[0]?.id,
|
|
@@ -765,7 +756,7 @@ async function main() {
|
|
|
765
756
|
console.error('[canon-codex] Ready — sessions created on demand');
|
|
766
757
|
await new Promise(() => { });
|
|
767
758
|
}
|
|
768
|
-
|
|
759
|
+
runCli(import.meta.url, main, (error) => {
|
|
769
760
|
console.error('[canon-codex] Fatal error:', error);
|
|
770
761
|
const activeProfile = getActiveProfile();
|
|
771
762
|
if (activeProfile)
|
package/dist/index.d.ts
ADDED
package/dist/index.js
ADDED
package/dist/register.d.ts
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
export
|
|
2
|
+
export declare function main(): Promise<void>;
|
package/dist/register.js
CHANGED
|
@@ -1,83 +1,90 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import { setDefaultResultOrder } from 'node:dns';
|
|
3
|
-
setDefaultResultOrder('ipv4first');
|
|
4
3
|
import { mkdirSync, readFileSync, writeFileSync } from 'node:fs';
|
|
5
4
|
import { homedir } from 'node:os';
|
|
6
5
|
import { join } from 'node:path';
|
|
7
6
|
import { parseArgs } from 'node:util';
|
|
8
7
|
import { registerAndWaitForApproval } from '@canonmsg/core';
|
|
8
|
+
import { runCli } from './cli-entry.js';
|
|
9
9
|
const CANON_DIR = join(homedir(), '.canon');
|
|
10
10
|
const AGENTS_PATH = join(CANON_DIR, 'agents.json');
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
profiles =
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
11
|
+
export async function main() {
|
|
12
|
+
setDefaultResultOrder('ipv4first');
|
|
13
|
+
const { values } = parseArgs({
|
|
14
|
+
options: {
|
|
15
|
+
name: { type: 'string' },
|
|
16
|
+
description: { type: 'string' },
|
|
17
|
+
phone: { type: 'string' },
|
|
18
|
+
profile: { type: 'string' },
|
|
19
|
+
'base-url': { type: 'string' },
|
|
20
|
+
},
|
|
21
|
+
strict: true,
|
|
22
|
+
});
|
|
23
|
+
if (!values.name || !values.description || !values.phone) {
|
|
24
|
+
console.error('Usage: canon-codex-register --name "Agent Name" --description "Description" --phone "+15551234567" [--profile "my-agent"]');
|
|
25
|
+
process.exit(1);
|
|
26
|
+
}
|
|
27
|
+
const profileName = values.profile || values.name.toLowerCase().replace(/[^a-z0-9-]/g, '-').replace(/-+/g, '-');
|
|
28
|
+
let existingAgentId;
|
|
29
|
+
try {
|
|
30
|
+
const profiles = JSON.parse(readFileSync(AGENTS_PATH, 'utf-8'));
|
|
31
|
+
existingAgentId = profiles[profileName]?.agentId;
|
|
32
|
+
}
|
|
33
|
+
catch {
|
|
34
|
+
// No existing profile state.
|
|
35
|
+
}
|
|
36
|
+
console.log(`Registering Codex agent "${values.name}" (profile: ${profileName})...`);
|
|
37
|
+
const result = await registerAndWaitForApproval({
|
|
38
|
+
name: values.name,
|
|
39
|
+
description: values.description,
|
|
40
|
+
ownerPhone: values.phone,
|
|
41
|
+
developerInfo: 'Codex host plugin',
|
|
42
|
+
clientType: 'codex',
|
|
43
|
+
baseUrl: values['base-url'],
|
|
44
|
+
requestedAgentId: existingAgentId,
|
|
45
|
+
}, {
|
|
46
|
+
onSubmitted: (requestId) => {
|
|
47
|
+
console.log(`Registration submitted (request ID: ${requestId}).`);
|
|
48
|
+
console.log('Waiting for approval in Canon app...');
|
|
49
|
+
},
|
|
50
|
+
onPollUpdate: () => {
|
|
51
|
+
process.stdout.write('.');
|
|
52
|
+
},
|
|
53
|
+
});
|
|
54
|
+
console.log('');
|
|
55
|
+
switch (result.status) {
|
|
56
|
+
case 'approved': {
|
|
57
|
+
mkdirSync(CANON_DIR, { recursive: true });
|
|
58
|
+
let profiles = {};
|
|
59
|
+
try {
|
|
60
|
+
profiles = JSON.parse(readFileSync(AGENTS_PATH, 'utf-8'));
|
|
61
|
+
}
|
|
62
|
+
catch {
|
|
63
|
+
// File does not exist yet.
|
|
64
|
+
}
|
|
65
|
+
profiles[profileName] = {
|
|
66
|
+
apiKey: result.apiKey,
|
|
67
|
+
agentId: result.agentId,
|
|
68
|
+
agentName: result.agentName,
|
|
69
|
+
registeredAt: new Date().toISOString(),
|
|
70
|
+
};
|
|
71
|
+
writeFileSync(AGENTS_PATH, JSON.stringify(profiles, null, 2));
|
|
72
|
+
console.log(`Approved! Agent: ${result.agentName} (${result.agentId})`);
|
|
73
|
+
console.log(`Saved as profile "${profileName}" in ~/.canon/agents.json`);
|
|
74
|
+
console.log('Start it with: canon-codex --cwd /path/to/project');
|
|
75
|
+
break;
|
|
62
76
|
}
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
console.log(`Saved as profile "${profileName}" in ~/.canon/agents.json`);
|
|
72
|
-
console.log('Start it with: canon-codex --cwd /path/to/project');
|
|
73
|
-
break;
|
|
77
|
+
case 'rejected':
|
|
78
|
+
console.log('Registration was rejected.');
|
|
79
|
+
process.exit(1);
|
|
80
|
+
break;
|
|
81
|
+
case 'timeout':
|
|
82
|
+
console.log('Registration timed out (5 minutes). Try again later.');
|
|
83
|
+
process.exit(1);
|
|
84
|
+
break;
|
|
74
85
|
}
|
|
75
|
-
case 'rejected':
|
|
76
|
-
console.log('Registration was rejected.');
|
|
77
|
-
process.exit(1);
|
|
78
|
-
break;
|
|
79
|
-
case 'timeout':
|
|
80
|
-
console.log('Registration timed out (5 minutes). Try again later.');
|
|
81
|
-
process.exit(1);
|
|
82
|
-
break;
|
|
83
86
|
}
|
|
87
|
+
runCli(import.meta.url, main, (error) => {
|
|
88
|
+
console.error('[canon-codex-register] Fatal error:', error);
|
|
89
|
+
process.exit(1);
|
|
90
|
+
});
|
package/dist/setup.d.ts
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
export
|
|
2
|
+
export declare function main(): void;
|
package/dist/setup.js
CHANGED
|
@@ -1,32 +1,39 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import { spawnSync } from 'node:child_process';
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
3
|
+
import { runCli } from './cli-entry.js';
|
|
4
|
+
export function main() {
|
|
5
|
+
console.log('Canon Codex Plugin Setup');
|
|
6
|
+
console.log('========================\n');
|
|
7
|
+
const version = spawnSync('codex', ['--version'], { encoding: 'utf-8' });
|
|
8
|
+
if (version.status === 0) {
|
|
9
|
+
console.log(`Detected Codex CLI: ${version.stdout.trim()}\n`);
|
|
10
|
+
}
|
|
11
|
+
else {
|
|
12
|
+
console.log('Codex CLI was not detected on PATH.');
|
|
13
|
+
console.log('Install Codex first, then rerun this setup.\n');
|
|
14
|
+
}
|
|
15
|
+
console.log('Next steps:');
|
|
16
|
+
console.log(' 0. Confirm Codex is logged in the way you want Canon to use');
|
|
17
|
+
console.log(' codex login status');
|
|
18
|
+
console.log('');
|
|
19
|
+
console.log(' 1. Register your agent');
|
|
20
|
+
console.log(' canon-codex-register --name "My Codex" --description "My local coding agent" --phone "+15551234567"');
|
|
21
|
+
console.log('');
|
|
22
|
+
console.log(' 2. Start the host in a project directory and keep it running');
|
|
23
|
+
console.log(' canon-codex --cwd /path/to/project');
|
|
24
|
+
console.log('');
|
|
25
|
+
console.log(' A git repo is not required; any readable directory works.');
|
|
26
|
+
console.log('');
|
|
27
|
+
console.log('Optional flags:');
|
|
28
|
+
console.log(' --model gpt-5.4');
|
|
29
|
+
console.log(' --sandbox workspace-write');
|
|
30
|
+
console.log(' --full-auto');
|
|
31
|
+
console.log('');
|
|
32
|
+
console.log('Note: recent Codex CLI versions use --full-auto for non-interactive write access.');
|
|
33
|
+
console.log('Note: Canon uses the local Codex login state by default (for example ChatGPT/device auth or API-key auth).');
|
|
34
|
+
console.log('Note: If Canon starts returning "Invalid API key", rerun canon-codex-register to replace the saved profile and then restart the host.');
|
|
8
35
|
}
|
|
9
|
-
|
|
10
|
-
console.
|
|
11
|
-
|
|
12
|
-
}
|
|
13
|
-
console.log('Next steps:');
|
|
14
|
-
console.log(' 0. Confirm Codex is logged in the way you want Canon to use');
|
|
15
|
-
console.log(' codex login status');
|
|
16
|
-
console.log('');
|
|
17
|
-
console.log(' 1. Register your agent');
|
|
18
|
-
console.log(' canon-codex-register --name "My Codex" --description "My local coding agent" --phone "+15551234567"');
|
|
19
|
-
console.log('');
|
|
20
|
-
console.log(' 2. Start the host in a project directory and keep it running');
|
|
21
|
-
console.log(' canon-codex --cwd /path/to/project');
|
|
22
|
-
console.log('');
|
|
23
|
-
console.log(' A git repo is not required; any readable directory works.');
|
|
24
|
-
console.log('');
|
|
25
|
-
console.log('Optional flags:');
|
|
26
|
-
console.log(' --model gpt-5.4');
|
|
27
|
-
console.log(' --sandbox workspace-write');
|
|
28
|
-
console.log(' --full-auto');
|
|
29
|
-
console.log('');
|
|
30
|
-
console.log('Note: recent Codex CLI versions use --full-auto for non-interactive write access.');
|
|
31
|
-
console.log('Note: Canon uses the local Codex login state by default (for example ChatGPT/device auth or API-key auth).');
|
|
32
|
-
console.log('Note: If Canon starts returning "Invalid API key", rerun canon-codex-register to replace the saved profile and then restart the host.');
|
|
36
|
+
runCli(import.meta.url, main, (error) => {
|
|
37
|
+
console.error('[canon-codex-setup] Fatal error:', error);
|
|
38
|
+
process.exit(1);
|
|
39
|
+
});
|
package/package.json
CHANGED
|
@@ -1,10 +1,16 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@canonmsg/codex-plugin",
|
|
3
|
-
"version": "0.6.
|
|
3
|
+
"version": "0.6.3",
|
|
4
4
|
"description": "Canon host integration for Codex CLI",
|
|
5
5
|
"type": "module",
|
|
6
|
-
"main": "dist/
|
|
7
|
-
"types": "dist/
|
|
6
|
+
"main": "dist/index.js",
|
|
7
|
+
"types": "dist/index.d.ts",
|
|
8
|
+
"exports": {
|
|
9
|
+
".": {
|
|
10
|
+
"types": "./dist/index.d.ts",
|
|
11
|
+
"default": "./dist/index.js"
|
|
12
|
+
}
|
|
13
|
+
},
|
|
8
14
|
"bin": {
|
|
9
15
|
"canon-codex": "dist/host.js",
|
|
10
16
|
"canon-codex-register": "dist/register.js",
|
|
@@ -23,7 +29,7 @@
|
|
|
23
29
|
},
|
|
24
30
|
"dependencies": {
|
|
25
31
|
"@canonmsg/agent-sdk": "^0.8.1",
|
|
26
|
-
"@canonmsg/core": "^0.7.
|
|
32
|
+
"@canonmsg/core": "^0.7.2"
|
|
27
33
|
},
|
|
28
34
|
"engines": {
|
|
29
35
|
"node": ">=18.0.0"
|