@canonmsg/codex-plugin 0.11.2 → 0.11.4
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 +5 -1
- package/dist/app-server-approval.d.ts +32 -0
- package/dist/app-server-approval.js +168 -0
- package/dist/host.js +33 -5
- package/dist/index.d.ts +2 -0
- package/dist/index.js +1 -0
- package/dist/permission-mode.d.ts +4 -0
- package/dist/permission-mode.js +6 -10
- package/package.json +3 -3
package/README.md
CHANGED
|
@@ -71,10 +71,12 @@ Advertise multiple project choices to the Canon app:
|
|
|
71
71
|
canon-codex --cwd ~/dev --workspace-root ~/dev
|
|
72
72
|
```
|
|
73
73
|
|
|
74
|
-
`--cwd` is the default workspace. Each `--workspace-root` value is an approved local root; the host discovers immediate child projects with common markers such as `.git`, `package.json`, `pyproject.toml`, `Cargo.toml`, or `go.mod` and publishes them as selectable projects during session creation. Use repeated `--workspace /path/to/project` entries to advertise specific projects outside those roots. Worktree mode creates a per-conversation git worktree under `~/.canon/conversation-worktrees`; shared-project mode runs directly in the selected directory.
|
|
74
|
+
`--cwd` is the default workspace. Each `--workspace-root` value is an approved local root; the host discovers immediate child projects with common markers such as `.git`, `package.json`, `pyproject.toml`, `Cargo.toml`, or `go.mod` and publishes them as selectable projects during session creation. Use repeated `--workspace /path/to/project` entries to advertise specific projects outside those roots. Worktree mode creates a best-effort per-conversation git worktree under `~/.canon/conversation-worktrees`; shared-project mode runs directly in the selected directory.
|
|
75
75
|
|
|
76
76
|
If worktree isolation is requested for a project that cannot support it, Canon may fall back to shared-project execution and surface the fallback reason in session details instead of failing the session outright.
|
|
77
77
|
|
|
78
|
+
Worktree mode is project isolation, not an operating-system sandbox. The Codex CLI sandbox and approval policy enforce actual file and command behavior.
|
|
79
|
+
|
|
78
80
|
Useful flags:
|
|
79
81
|
|
|
80
82
|
```bash
|
|
@@ -85,6 +87,8 @@ Codex also supports `--add-dir /extra/path` for additional writable directories
|
|
|
85
87
|
|
|
86
88
|
Recent Codex CLI releases no longer accept `--ask-for-approval` with `codex exec`. If you previously launched Canon with `--sandbox workspace-write --ask-for-approval never`, switch to `--full-auto`.
|
|
87
89
|
|
|
90
|
+
Do not start Canon with `--sandbox danger-full-access` as an unlabeled default. Use `--dangerously-bypass-approvals-and-sandbox` only when you intentionally want Canon to advertise the owner-only Bypass policy.
|
|
91
|
+
|
|
88
92
|
Local smoke test:
|
|
89
93
|
|
|
90
94
|
```bash
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import type { ApprovalNativeRequestMetadata, ApprovalRequestCategory, ApprovalRequestDetail, ApprovalResult, ApprovalRisk } from '@canonmsg/core';
|
|
2
|
+
/**
|
|
3
|
+
* Mapping for Codex app-server native approval requests.
|
|
4
|
+
*
|
|
5
|
+
* The current Canon Codex host still uses `codex exec --json`, which cannot
|
|
6
|
+
* block on these methods. Keep this as metadata/decision plumbing for the
|
|
7
|
+
* future app-server transport, not as an advertised `exec --json` UI mode.
|
|
8
|
+
*/
|
|
9
|
+
export type CodexAppServerApprovalMethod = 'item/commandExecution/requestApproval' | 'item/fileChange/requestApproval' | 'item/permissions/requestApproval' | 'execCommandApproval' | 'applyPatchApproval';
|
|
10
|
+
export interface CodexNativeApprovalRequest {
|
|
11
|
+
method: CodexAppServerApprovalMethod;
|
|
12
|
+
params: Record<string, unknown>;
|
|
13
|
+
}
|
|
14
|
+
export interface CanonCodexApprovalRequest {
|
|
15
|
+
toolName: string;
|
|
16
|
+
toolInput: Record<string, unknown>;
|
|
17
|
+
toolSummary: string;
|
|
18
|
+
category: ApprovalRequestCategory;
|
|
19
|
+
risk: ApprovalRisk;
|
|
20
|
+
riskLevel: 'normal' | 'destructive';
|
|
21
|
+
native: ApprovalNativeRequestMetadata;
|
|
22
|
+
details: ApprovalRequestDetail[];
|
|
23
|
+
}
|
|
24
|
+
export type CodexApprovalDecision = {
|
|
25
|
+
decision: 'accept';
|
|
26
|
+
} | {
|
|
27
|
+
decision: 'acceptForSession';
|
|
28
|
+
} | {
|
|
29
|
+
decision: 'decline';
|
|
30
|
+
};
|
|
31
|
+
export declare function mapCodexAppServerApprovalRequest(request: CodexNativeApprovalRequest): CanonCodexApprovalRequest | null;
|
|
32
|
+
export declare function mapCanonApprovalResultToCodexDecision(result: ApprovalResult): CodexApprovalDecision;
|
|
@@ -0,0 +1,168 @@
|
|
|
1
|
+
function isRecord(value) {
|
|
2
|
+
return Boolean(value && typeof value === 'object' && !Array.isArray(value));
|
|
3
|
+
}
|
|
4
|
+
function readString(record, keys) {
|
|
5
|
+
for (const key of keys) {
|
|
6
|
+
const value = record[key];
|
|
7
|
+
if (typeof value === 'string' && value.trim())
|
|
8
|
+
return value.trim();
|
|
9
|
+
}
|
|
10
|
+
return undefined;
|
|
11
|
+
}
|
|
12
|
+
function firstString(value) {
|
|
13
|
+
if (typeof value === 'string' && value.trim())
|
|
14
|
+
return value.trim();
|
|
15
|
+
if (Array.isArray(value)) {
|
|
16
|
+
return value.map((entry) => (typeof entry === 'string' ? entry.trim() : '')).find(Boolean);
|
|
17
|
+
}
|
|
18
|
+
return undefined;
|
|
19
|
+
}
|
|
20
|
+
function stringifyPreview(value, maxLength = 500) {
|
|
21
|
+
if (typeof value === 'string')
|
|
22
|
+
return value.length > maxLength ? `${value.slice(0, maxLength - 3)}...` : value;
|
|
23
|
+
const json = JSON.stringify(value ?? {});
|
|
24
|
+
return json.length > maxLength ? `${json.slice(0, maxLength - 3)}...` : json;
|
|
25
|
+
}
|
|
26
|
+
function nativeHandle(method, params) {
|
|
27
|
+
const native = {
|
|
28
|
+
runtime: 'codex',
|
|
29
|
+
method,
|
|
30
|
+
};
|
|
31
|
+
const requestId = readString(params, ['requestId', 'id', 'callId']);
|
|
32
|
+
const provider = readString(params, ['provider']);
|
|
33
|
+
const origin = readString(params, ['origin']);
|
|
34
|
+
const surface = readString(params, ['surface']);
|
|
35
|
+
const threadId = readString(params, ['threadId', 'conversationId']);
|
|
36
|
+
const turnId = readString(params, ['turnId']);
|
|
37
|
+
const runId = readString(params, ['runId']);
|
|
38
|
+
const itemId = readString(params, ['itemId']);
|
|
39
|
+
const toolCallId = readString(params, ['toolCallId', 'toolUseId']);
|
|
40
|
+
const approvalId = readString(params, ['approvalId']);
|
|
41
|
+
const pluginId = readString(params, ['pluginId']);
|
|
42
|
+
const sessionKey = readString(params, ['sessionKey', 'sessionId']);
|
|
43
|
+
const cwd = readString(params, ['cwd', 'workingDirectory']);
|
|
44
|
+
const model = readString(params, ['model']);
|
|
45
|
+
if (requestId)
|
|
46
|
+
native.requestId = requestId;
|
|
47
|
+
if (provider)
|
|
48
|
+
native.provider = provider;
|
|
49
|
+
if (origin)
|
|
50
|
+
native.origin = origin;
|
|
51
|
+
if (surface)
|
|
52
|
+
native.surface = surface;
|
|
53
|
+
if (threadId)
|
|
54
|
+
native.threadId = threadId;
|
|
55
|
+
if (turnId)
|
|
56
|
+
native.turnId = turnId;
|
|
57
|
+
if (runId)
|
|
58
|
+
native.runId = runId;
|
|
59
|
+
if (itemId)
|
|
60
|
+
native.itemId = itemId;
|
|
61
|
+
if (toolCallId)
|
|
62
|
+
native.toolCallId = toolCallId;
|
|
63
|
+
if (approvalId)
|
|
64
|
+
native.approvalId = approvalId;
|
|
65
|
+
if (pluginId)
|
|
66
|
+
native.pluginId = pluginId;
|
|
67
|
+
if (sessionKey)
|
|
68
|
+
native.sessionKey = sessionKey;
|
|
69
|
+
if (cwd)
|
|
70
|
+
native.cwd = cwd;
|
|
71
|
+
if (model)
|
|
72
|
+
native.model = model;
|
|
73
|
+
return native;
|
|
74
|
+
}
|
|
75
|
+
function commandRisk(command) {
|
|
76
|
+
if (/\brm\s+(-rf?|--force)|\bgit\s+reset\s+--hard|\bgit\s+push\s+--force|\bdrop\s+(table|database)\b/i.test(command)) {
|
|
77
|
+
return 'destructive';
|
|
78
|
+
}
|
|
79
|
+
return 'high';
|
|
80
|
+
}
|
|
81
|
+
function detail(label, value, monospace = true) {
|
|
82
|
+
return value ? [{ label, value, monospace }] : [];
|
|
83
|
+
}
|
|
84
|
+
function mapCommandRequest(method, params) {
|
|
85
|
+
const command = readString(params, ['command', 'cmd'])
|
|
86
|
+
?? firstString(params.argv)
|
|
87
|
+
?? firstString(params.args)
|
|
88
|
+
?? stringifyPreview(params);
|
|
89
|
+
const cwd = readString(params, ['cwd', 'workingDirectory']);
|
|
90
|
+
const risk = commandRisk(command);
|
|
91
|
+
return {
|
|
92
|
+
toolName: 'codex.command',
|
|
93
|
+
toolInput: {
|
|
94
|
+
command,
|
|
95
|
+
...(cwd ? { cwd } : {}),
|
|
96
|
+
},
|
|
97
|
+
toolSummary: command,
|
|
98
|
+
category: 'command',
|
|
99
|
+
risk,
|
|
100
|
+
riskLevel: risk === 'destructive' ? 'destructive' : 'normal',
|
|
101
|
+
native: nativeHandle(method, params),
|
|
102
|
+
details: [
|
|
103
|
+
...detail('Command', command),
|
|
104
|
+
...detail('Working directory', cwd),
|
|
105
|
+
],
|
|
106
|
+
};
|
|
107
|
+
}
|
|
108
|
+
function mapFileRequest(method, params) {
|
|
109
|
+
const path = readString(params, ['path', 'filePath', 'targetPath']);
|
|
110
|
+
const summary = path
|
|
111
|
+
?? firstString(params.paths)
|
|
112
|
+
?? stringifyPreview(params.changes ?? params.patch ?? params);
|
|
113
|
+
const patch = readString(params, ['patch', 'diff']);
|
|
114
|
+
const destructive = /\b(delete|remove|unlink)\b/i.test(stringifyPreview(params, 1000));
|
|
115
|
+
return {
|
|
116
|
+
toolName: method === 'applyPatchApproval' ? 'codex.applyPatch' : 'codex.fileChange',
|
|
117
|
+
toolInput: {
|
|
118
|
+
...(path ? { path } : {}),
|
|
119
|
+
...(patch ? { patch } : {}),
|
|
120
|
+
},
|
|
121
|
+
toolSummary: summary,
|
|
122
|
+
category: 'file',
|
|
123
|
+
risk: destructive ? 'destructive' : 'high',
|
|
124
|
+
riskLevel: destructive ? 'destructive' : 'normal',
|
|
125
|
+
native: nativeHandle(method, params),
|
|
126
|
+
details: [
|
|
127
|
+
...detail('Path', path),
|
|
128
|
+
...detail('Patch', patch),
|
|
129
|
+
],
|
|
130
|
+
};
|
|
131
|
+
}
|
|
132
|
+
function mapPermissionRequest(method, params) {
|
|
133
|
+
const action = readString(params, ['action', 'permission', 'toolName', 'tool'])
|
|
134
|
+
?? stringifyPreview(params);
|
|
135
|
+
return {
|
|
136
|
+
toolName: 'codex.permission',
|
|
137
|
+
toolInput: { action },
|
|
138
|
+
toolSummary: action,
|
|
139
|
+
category: 'tool',
|
|
140
|
+
risk: 'normal',
|
|
141
|
+
riskLevel: 'normal',
|
|
142
|
+
native: nativeHandle(method, params),
|
|
143
|
+
details: detail('Permission', action, false),
|
|
144
|
+
};
|
|
145
|
+
}
|
|
146
|
+
export function mapCodexAppServerApprovalRequest(request) {
|
|
147
|
+
if (!isRecord(request.params))
|
|
148
|
+
return null;
|
|
149
|
+
if (request.method === 'item/commandExecution/requestApproval'
|
|
150
|
+
|| request.method === 'execCommandApproval') {
|
|
151
|
+
return mapCommandRequest(request.method, request.params);
|
|
152
|
+
}
|
|
153
|
+
if (request.method === 'item/fileChange/requestApproval'
|
|
154
|
+
|| request.method === 'applyPatchApproval') {
|
|
155
|
+
return mapFileRequest(request.method, request.params);
|
|
156
|
+
}
|
|
157
|
+
if (request.method === 'item/permissions/requestApproval') {
|
|
158
|
+
return mapPermissionRequest(request.method, request.params);
|
|
159
|
+
}
|
|
160
|
+
return null;
|
|
161
|
+
}
|
|
162
|
+
export function mapCanonApprovalResultToCodexDecision(result) {
|
|
163
|
+
if (result.decision === 'deny')
|
|
164
|
+
return { decision: 'decline' };
|
|
165
|
+
if (result.sessionRule)
|
|
166
|
+
return { decision: 'acceptForSession' };
|
|
167
|
+
return { decision: 'accept' };
|
|
168
|
+
}
|
package/dist/host.js
CHANGED
|
@@ -3,7 +3,7 @@ import { setDefaultResultOrder } from 'node:dns';
|
|
|
3
3
|
import { randomUUID } from 'node:crypto';
|
|
4
4
|
import { dirname } from 'node:path';
|
|
5
5
|
import { parseArgs } from 'node:util';
|
|
6
|
-
import { getCodexImagePath, materializeMessageMedia, } from '@canonmsg/agent-sdk';
|
|
6
|
+
import { getCodexImagePath, materializeMessageMedia, materializeReplyContextMedia, } from '@canonmsg/agent-sdk';
|
|
7
7
|
import { RUNTIME_NEW_SESSION_ACTION, RUNTIME_STOP_ACTION, RUNTIME_STOP_AND_DROP_ACTION, buildCanonHostPrompt, buildConfiguredWorkspaceOptionsWithRoots, buildFirstPartyCodingRuntimeDescriptor, buildHydratedInboundContext, diffCanonMemberIds, buildPublicWorkspaceRoots, buildPublicWorkspaceOptions, createConversationMetadataLoader, createRuntimeStatePublisher, EXECUTION_ENVIRONMENT_MODES, ExecutionEnvironmentError, CanonClient, CanonStream, DEFAULT_PARTICIPATION_HISTORY_FETCH_LIMIT, DEFAULT_RUNTIME_CAPABILITIES, FINAL_MESSAGE_HANDOFF_MS, getActiveProfileLock, initRTDBAuth, buildLocalRuntimeId, heartbeatLocalRuntimeEntry, loadRuntimeSessionState, markLocalRuntimeStopped, normalizeTurnMetadata, normalizeTurnState, prepareConversationEnvironment, loadHostSessionConfig, releaseConversationEnvironment, resolveCanonAgent, rtdbRead, rtdbWrite, shouldTriggerAgentTurn, saveRuntimeSessionState, publishHostAgentRuntime, publishHostSessionSnapshots, renderCanonHostInboundContent, resolveHostWorkspaceCwd, upsertLocalRuntimeEntry, } from '@canonmsg/core';
|
|
8
8
|
import { buildInboundContextLines, decideAutoReply, } from './inbound-policy.js';
|
|
9
9
|
import { CodexConversationAdapter, } from './adapter.js';
|
|
@@ -202,6 +202,21 @@ function buildCanonPrompt(input) {
|
|
|
202
202
|
function renderInboundContent(message, materialized) {
|
|
203
203
|
return renderCanonHostInboundContent(message, materialized);
|
|
204
204
|
}
|
|
205
|
+
async function materializePromptReplyContext(input) {
|
|
206
|
+
if (!input.replyContext?.found || !input.replyContext.attachments?.length) {
|
|
207
|
+
return { replyContext: input.replyContext, materialized: [] };
|
|
208
|
+
}
|
|
209
|
+
try {
|
|
210
|
+
return await materializeReplyContextMedia(input.replyContext, {
|
|
211
|
+
agentId: input.agentId,
|
|
212
|
+
conversationId: input.conversationId,
|
|
213
|
+
});
|
|
214
|
+
}
|
|
215
|
+
catch (error) {
|
|
216
|
+
console.error(`${input.logPrefix} Failed to materialize replied-to media:`, error instanceof Error ? error.message : error);
|
|
217
|
+
return { replyContext: input.replyContext, materialized: [] };
|
|
218
|
+
}
|
|
219
|
+
}
|
|
205
220
|
function summarizeCommand(command) {
|
|
206
221
|
const trimmed = command.trim();
|
|
207
222
|
if (!trimmed)
|
|
@@ -398,10 +413,12 @@ export async function main() {
|
|
|
398
413
|
]);
|
|
399
414
|
return buildHydratedInboundContext({
|
|
400
415
|
agentId,
|
|
416
|
+
conversationId: input.conversationId,
|
|
401
417
|
conversation,
|
|
402
418
|
page,
|
|
403
419
|
activeSelfContextId: input.activeSelfContextId,
|
|
404
420
|
selfContexts: input.selfContexts,
|
|
421
|
+
provenance: input.provenance,
|
|
405
422
|
message: input.message,
|
|
406
423
|
senderName: input.senderName,
|
|
407
424
|
isOwner: input.isOwner,
|
|
@@ -664,10 +681,6 @@ export async function main() {
|
|
|
664
681
|
console.error(`[canon-codex] [${input.conversationId.slice(0, 8)}] Failed to materialize media:`, error instanceof Error ? error.message : error);
|
|
665
682
|
}
|
|
666
683
|
}
|
|
667
|
-
const imagePaths = materialized
|
|
668
|
-
.map((attachment) => getCodexImagePath(attachment))
|
|
669
|
-
.filter((path) => path !== null);
|
|
670
|
-
const mediaAddDirs = uniqueStrings(materialized.map((attachment) => dirname(attachment.path)));
|
|
671
684
|
const content = renderInboundContent(input.message, materialized);
|
|
672
685
|
const hydrated = await loadHydratedInboundContext({
|
|
673
686
|
conversationId: input.conversationId,
|
|
@@ -676,11 +689,24 @@ export async function main() {
|
|
|
676
689
|
isOwner: input.isOwner,
|
|
677
690
|
activeSelfContextId: input.activeSelfContextId,
|
|
678
691
|
selfContexts: input.selfContexts,
|
|
692
|
+
provenance: input.provenance,
|
|
679
693
|
hydratedPage: input.hydratedPage,
|
|
680
694
|
});
|
|
681
695
|
const behavior = input.behavior ?? hydrated.behavior;
|
|
682
696
|
const activeSelfContextId = hydrated.activeSelfContextId;
|
|
683
697
|
const selfContexts = hydrated.selfContexts;
|
|
698
|
+
const replyMedia = await materializePromptReplyContext({
|
|
699
|
+
replyContext: hydrated.replyContext,
|
|
700
|
+
agentId,
|
|
701
|
+
conversationId: input.conversationId,
|
|
702
|
+
logPrefix: `[canon-codex] [${input.conversationId.slice(0, 8)}]`,
|
|
703
|
+
});
|
|
704
|
+
const replyContext = replyMedia.replyContext;
|
|
705
|
+
const promptMaterialized = [...replyMedia.materialized, ...materialized];
|
|
706
|
+
const imagePaths = promptMaterialized
|
|
707
|
+
.map((attachment) => getCodexImagePath(attachment))
|
|
708
|
+
.filter((path) => path !== null);
|
|
709
|
+
const mediaAddDirs = uniqueStrings(promptMaterialized.map((attachment) => dirname(attachment.path)));
|
|
684
710
|
const participantContext = hydrated.participantContext;
|
|
685
711
|
const autoReply = decideAutoReply(participantContext, behavior);
|
|
686
712
|
if (!autoReply.allow) {
|
|
@@ -717,6 +743,7 @@ export async function main() {
|
|
|
717
743
|
participantContext,
|
|
718
744
|
behavior,
|
|
719
745
|
selfContexts,
|
|
746
|
+
replyContext,
|
|
720
747
|
});
|
|
721
748
|
if (session.running && deliveryIntent === 'interrupt') {
|
|
722
749
|
enqueuePrompt(session, prompt, deliveryIntent, true, input.message.id, shouldMarkAccepted, imagePaths, mediaAddDirs);
|
|
@@ -1072,6 +1099,7 @@ export async function main() {
|
|
|
1072
1099
|
behavior: payload.behavior,
|
|
1073
1100
|
activeSelfContextId: payload.activeSelfContextId,
|
|
1074
1101
|
selfContexts: payload.selfContexts,
|
|
1102
|
+
provenance: payload.provenance,
|
|
1075
1103
|
});
|
|
1076
1104
|
if (message.id) {
|
|
1077
1105
|
saveRuntimeSessionState(runtimeId, {
|
package/dist/index.d.ts
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
1
|
export { main as hostMain } from './host.js';
|
|
2
2
|
export { main as registerMain } from './register.js';
|
|
3
3
|
export { main as setupMain } from './setup.js';
|
|
4
|
+
export { mapCanonApprovalResultToCodexDecision, mapCodexAppServerApprovalRequest, } from './app-server-approval.js';
|
|
5
|
+
export type { CanonCodexApprovalRequest, CodexAppServerApprovalMethod, CodexApprovalDecision, CodexNativeApprovalRequest, } from './app-server-approval.js';
|
package/dist/index.js
CHANGED
|
@@ -2,15 +2,19 @@ import type { CodexSandboxMode } from './adapter.js';
|
|
|
2
2
|
export declare const CODEX_PERMISSION_OPTIONS: readonly [{
|
|
3
3
|
readonly value: "readonly";
|
|
4
4
|
readonly label: "Read-only";
|
|
5
|
+
readonly description: "Inspect files and project state without making changes.";
|
|
5
6
|
}, {
|
|
6
7
|
readonly value: "workspace";
|
|
7
8
|
readonly label: "Workspace-write";
|
|
9
|
+
readonly description: "Edit inside the selected workspace using Codex CLI sandboxing.";
|
|
8
10
|
}, {
|
|
9
11
|
readonly value: "full-auto";
|
|
10
12
|
readonly label: "Full auto";
|
|
13
|
+
readonly description: "Automatically run workspace-write actions with lower-friction approvals.";
|
|
11
14
|
}, {
|
|
12
15
|
readonly value: "bypass";
|
|
13
16
|
readonly label: "Bypass (dangerous)";
|
|
17
|
+
readonly description: "Skip Codex approval and sandbox protection. Use only in an externally sandboxed environment.";
|
|
14
18
|
}];
|
|
15
19
|
export type CodexPermissionMode = (typeof CODEX_PERMISSION_OPTIONS)[number]['value'];
|
|
16
20
|
export type CodexApprovalShape = {
|
package/dist/permission-mode.js
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
export const CODEX_PERMISSION_OPTIONS = [
|
|
2
|
-
{ value: 'readonly', label: 'Read-only' },
|
|
3
|
-
{ value: 'workspace', label: 'Workspace-write' },
|
|
4
|
-
{ value: 'full-auto', label: 'Full auto' },
|
|
5
|
-
{ value: 'bypass', label: 'Bypass (dangerous)' },
|
|
2
|
+
{ value: 'readonly', label: 'Read-only', description: 'Inspect files and project state without making changes.' },
|
|
3
|
+
{ value: 'workspace', label: 'Workspace-write', description: 'Edit inside the selected workspace using Codex CLI sandboxing.' },
|
|
4
|
+
{ value: 'full-auto', label: 'Full auto', description: 'Automatically run workspace-write actions with lower-friction approvals.' },
|
|
5
|
+
{ value: 'bypass', label: 'Bypass (dangerous)', description: 'Skip Codex approval and sandbox protection. Use only in an externally sandboxed environment.' },
|
|
6
6
|
];
|
|
7
7
|
export function mapCanonPermissionToCodex(mode) {
|
|
8
8
|
switch (mode) {
|
|
@@ -29,12 +29,8 @@ export function deriveCodexPermissionEnvelope(args) {
|
|
|
29
29
|
availablePermissionModes: codexOptionsThrough('readonly'),
|
|
30
30
|
};
|
|
31
31
|
}
|
|
32
|
-
if (args.sandbox === 'danger-full-access') {
|
|
33
|
-
|
|
34
|
-
availablePermissionModes: args['dangerously-bypass-approvals-and-sandbox']
|
|
35
|
-
? [...CODEX_PERMISSION_OPTIONS]
|
|
36
|
-
: codexOptionsThrough('full-auto'),
|
|
37
|
-
};
|
|
32
|
+
if (args.sandbox === 'danger-full-access' && !args['dangerously-bypass-approvals-and-sandbox']) {
|
|
33
|
+
throw new Error('Codex sandbox danger-full-access cannot be used as an unlabeled Canon default. Use --dangerously-bypass-approvals-and-sandbox if you intentionally want the Bypass policy.');
|
|
38
34
|
}
|
|
39
35
|
if (args['dangerously-bypass-approvals-and-sandbox']) {
|
|
40
36
|
return {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@canonmsg/codex-plugin",
|
|
3
|
-
"version": "0.11.
|
|
3
|
+
"version": "0.11.4",
|
|
4
4
|
"description": "Canon host integration for Codex CLI",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/index.js",
|
|
@@ -29,8 +29,8 @@
|
|
|
29
29
|
"prepack": "npm run build"
|
|
30
30
|
},
|
|
31
31
|
"dependencies": {
|
|
32
|
-
"@canonmsg/agent-sdk": "^1.
|
|
33
|
-
"@canonmsg/core": "^0.
|
|
32
|
+
"@canonmsg/agent-sdk": "^1.5.0",
|
|
33
|
+
"@canonmsg/core": "^0.19.0"
|
|
34
34
|
},
|
|
35
35
|
"engines": {
|
|
36
36
|
"node": ">=18.0.0"
|