@canonmsg/codex-plugin 0.11.2 → 0.11.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/README.md +5 -1
- package/dist/host.js +29 -5
- 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
|
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)
|
|
@@ -664,10 +679,6 @@ export async function main() {
|
|
|
664
679
|
console.error(`[canon-codex] [${input.conversationId.slice(0, 8)}] Failed to materialize media:`, error instanceof Error ? error.message : error);
|
|
665
680
|
}
|
|
666
681
|
}
|
|
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
682
|
const content = renderInboundContent(input.message, materialized);
|
|
672
683
|
const hydrated = await loadHydratedInboundContext({
|
|
673
684
|
conversationId: input.conversationId,
|
|
@@ -681,6 +692,18 @@ export async function main() {
|
|
|
681
692
|
const behavior = input.behavior ?? hydrated.behavior;
|
|
682
693
|
const activeSelfContextId = hydrated.activeSelfContextId;
|
|
683
694
|
const selfContexts = hydrated.selfContexts;
|
|
695
|
+
const replyMedia = await materializePromptReplyContext({
|
|
696
|
+
replyContext: hydrated.replyContext,
|
|
697
|
+
agentId,
|
|
698
|
+
conversationId: input.conversationId,
|
|
699
|
+
logPrefix: `[canon-codex] [${input.conversationId.slice(0, 8)}]`,
|
|
700
|
+
});
|
|
701
|
+
const replyContext = replyMedia.replyContext;
|
|
702
|
+
const promptMaterialized = [...replyMedia.materialized, ...materialized];
|
|
703
|
+
const imagePaths = promptMaterialized
|
|
704
|
+
.map((attachment) => getCodexImagePath(attachment))
|
|
705
|
+
.filter((path) => path !== null);
|
|
706
|
+
const mediaAddDirs = uniqueStrings(promptMaterialized.map((attachment) => dirname(attachment.path)));
|
|
684
707
|
const participantContext = hydrated.participantContext;
|
|
685
708
|
const autoReply = decideAutoReply(participantContext, behavior);
|
|
686
709
|
if (!autoReply.allow) {
|
|
@@ -717,6 +740,7 @@ export async function main() {
|
|
|
717
740
|
participantContext,
|
|
718
741
|
behavior,
|
|
719
742
|
selfContexts,
|
|
743
|
+
replyContext,
|
|
720
744
|
});
|
|
721
745
|
if (session.running && deliveryIntent === 'interrupt') {
|
|
722
746
|
enqueuePrompt(session, prompt, deliveryIntent, true, input.message.id, shouldMarkAccepted, imagePaths, mediaAddDirs);
|
|
@@ -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.3",
|
|
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.4.
|
|
33
|
-
"@canonmsg/core": "^0.18.
|
|
32
|
+
"@canonmsg/agent-sdk": "^1.4.1",
|
|
33
|
+
"@canonmsg/core": "^0.18.1"
|
|
34
34
|
},
|
|
35
35
|
"engines": {
|
|
36
36
|
"node": ">=18.0.0"
|