agentcord 0.2.0 → 2.1.1
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/.env.example
CHANGED
|
@@ -16,6 +16,11 @@ DISCORD_CLIENT_ID=your-application-client-id
|
|
|
16
16
|
# Optional: Default working directory for new sessions
|
|
17
17
|
# DEFAULT_DIRECTORY=/Users/me/Dev
|
|
18
18
|
|
|
19
|
+
# Optional: Default Codex execution policy for new/resumed Codex sessions
|
|
20
|
+
# CODEX_SANDBOX_MODE=workspace-write # read-only | workspace-write | danger-full-access
|
|
21
|
+
# CODEX_APPROVAL_POLICY=on-request # never | on-request | on-failure | untrusted
|
|
22
|
+
# CODEX_NETWORK_ACCESS_ENABLED=true # true | false
|
|
23
|
+
|
|
19
24
|
# Optional: Auto-delete messages older than N days
|
|
20
25
|
# MESSAGE_RETENTION_DAYS=7
|
|
21
26
|
|
package/README.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# agentcord
|
|
2
2
|
|
|
3
|
-
Run and manage AI coding agent sessions on your machine through Discord.
|
|
3
|
+
Run and manage AI coding agent sessions on your machine through Discord. Supports [Claude Code](https://docs.anthropic.com/en/docs/claude-code) and OpenAI Codex.
|
|
4
4
|
|
|
5
5
|
Each session gets a Discord channel for chatting with the agent and a tmux session for direct terminal access. Sessions are organized by project — create multiple sessions in the same codebase, each with their own channel.
|
|
6
6
|
|
|
@@ -20,6 +20,7 @@ The setup wizard walks you through creating a Discord app, configuring the bot t
|
|
|
20
20
|
- **Node.js 22.6+** (uses native TypeScript execution)
|
|
21
21
|
- **tmux** (for terminal session access)
|
|
22
22
|
- **Claude Code** installed on the machine (`@anthropic-ai/claude-agent-sdk`)
|
|
23
|
+
- **OpenAI Codex SDK** for Codex sessions (`@openai/codex-sdk`)
|
|
23
24
|
|
|
24
25
|
## How It Works
|
|
25
26
|
|
|
@@ -58,7 +59,7 @@ Discord Server
|
|
|
58
59
|
| `/claude attach` | Show tmux attach command for terminal access |
|
|
59
60
|
| `/claude model <model>` | Change model for the session |
|
|
60
61
|
| `/claude verbose` | Toggle tool call/result visibility |
|
|
61
|
-
| `/claude sync` | Reconnect orphaned tmux
|
|
62
|
+
| `/claude sync` | Reconnect orphaned sessions (tmux + existing provider channels, including Codex) |
|
|
62
63
|
|
|
63
64
|
### Shell
|
|
64
65
|
|
|
@@ -118,8 +119,15 @@ ALLOWED_USERS=123456789,987654321 # Comma-separated user IDs
|
|
|
118
119
|
ALLOW_ALL_USERS=false # Or true to skip whitelist
|
|
119
120
|
ALLOWED_PATHS=/Users/me/Dev # Restrict accessible directories
|
|
120
121
|
DEFAULT_DIRECTORY=/Users/me/Dev # Default for new sessions
|
|
122
|
+
CODEX_SANDBOX_MODE=workspace-write # read-only | workspace-write | danger-full-access
|
|
123
|
+
CODEX_APPROVAL_POLICY=on-request # never | on-request | on-failure | untrusted
|
|
124
|
+
CODEX_NETWORK_ACCESS_ENABLED=true # true | false
|
|
121
125
|
```
|
|
122
126
|
|
|
127
|
+
You can also override Codex policy per session when creating/resuming via:
|
|
128
|
+
- `/session new ... sandbox-mode:<mode> approval-policy:<policy> network-access:<bool>`
|
|
129
|
+
- `/session resume ... sandbox-mode:<mode> approval-policy:<policy> network-access:<bool>`
|
|
130
|
+
|
|
123
131
|
## Development
|
|
124
132
|
|
|
125
133
|
```bash
|
|
@@ -20,6 +20,45 @@ function getEnvOrExit(name) {
|
|
|
20
20
|
}
|
|
21
21
|
return value;
|
|
22
22
|
}
|
|
23
|
+
var CODEX_SANDBOX_MODES = /* @__PURE__ */ new Set([
|
|
24
|
+
"read-only",
|
|
25
|
+
"workspace-write",
|
|
26
|
+
"danger-full-access"
|
|
27
|
+
]);
|
|
28
|
+
var CODEX_APPROVAL_POLICIES = /* @__PURE__ */ new Set([
|
|
29
|
+
"never",
|
|
30
|
+
"on-request",
|
|
31
|
+
"on-failure",
|
|
32
|
+
"untrusted"
|
|
33
|
+
]);
|
|
34
|
+
function parseCodexSandboxMode(value) {
|
|
35
|
+
if (!value) return void 0;
|
|
36
|
+
if (CODEX_SANDBOX_MODES.has(value)) {
|
|
37
|
+
return value;
|
|
38
|
+
}
|
|
39
|
+
console.error(
|
|
40
|
+
`ERROR: Invalid CODEX_SANDBOX_MODE "${value}". Expected one of: ${Array.from(CODEX_SANDBOX_MODES).join(", ")}`
|
|
41
|
+
);
|
|
42
|
+
process.exit(1);
|
|
43
|
+
}
|
|
44
|
+
function parseCodexApprovalPolicy(value) {
|
|
45
|
+
if (!value) return void 0;
|
|
46
|
+
if (CODEX_APPROVAL_POLICIES.has(value)) {
|
|
47
|
+
return value;
|
|
48
|
+
}
|
|
49
|
+
console.error(
|
|
50
|
+
`ERROR: Invalid CODEX_APPROVAL_POLICY "${value}". Expected one of: ${Array.from(CODEX_APPROVAL_POLICIES).join(", ")}`
|
|
51
|
+
);
|
|
52
|
+
process.exit(1);
|
|
53
|
+
}
|
|
54
|
+
function parseBoolean(name, value) {
|
|
55
|
+
if (value === void 0) return void 0;
|
|
56
|
+
const normalized = value.trim().toLowerCase();
|
|
57
|
+
if (normalized === "true") return true;
|
|
58
|
+
if (normalized === "false") return false;
|
|
59
|
+
console.error(`ERROR: Invalid ${name} "${value}". Expected "true" or "false".`);
|
|
60
|
+
process.exit(1);
|
|
61
|
+
}
|
|
23
62
|
var config = {
|
|
24
63
|
token: getEnvOrExit("DISCORD_TOKEN"),
|
|
25
64
|
clientId: getEnvOrExit("DISCORD_CLIENT_ID"),
|
|
@@ -29,7 +68,10 @@ var config = {
|
|
|
29
68
|
allowedPaths: process.env.ALLOWED_PATHS?.split(",").map((p) => p.trim()).filter(Boolean) || [],
|
|
30
69
|
defaultDirectory: process.env.DEFAULT_DIRECTORY || process.cwd(),
|
|
31
70
|
messageRetentionDays: process.env.MESSAGE_RETENTION_DAYS ? parseInt(process.env.MESSAGE_RETENTION_DAYS, 10) : null,
|
|
32
|
-
rateLimitMs: process.env.RATE_LIMIT_MS ? parseInt(process.env.RATE_LIMIT_MS, 10) : 1e3
|
|
71
|
+
rateLimitMs: process.env.RATE_LIMIT_MS ? parseInt(process.env.RATE_LIMIT_MS, 10) : 1e3,
|
|
72
|
+
codexSandboxMode: parseCodexSandboxMode(process.env.CODEX_SANDBOX_MODE),
|
|
73
|
+
codexApprovalPolicy: parseCodexApprovalPolicy(process.env.CODEX_APPROVAL_POLICY),
|
|
74
|
+
codexNetworkAccessEnabled: parseBoolean("CODEX_NETWORK_ACCESS_ENABLED", process.env.CODEX_NETWORK_ACCESS_ENABLED)
|
|
33
75
|
};
|
|
34
76
|
if (config.allowedUsers.length > 0) {
|
|
35
77
|
console.log(`User whitelist: ${config.allowedUsers.length} user(s) allowed`);
|
|
@@ -45,6 +87,13 @@ if (config.allowedPaths.length > 0) {
|
|
|
45
87
|
if (config.messageRetentionDays) {
|
|
46
88
|
console.log(`Message retention: ${config.messageRetentionDays} day(s)`);
|
|
47
89
|
}
|
|
90
|
+
if (config.codexSandboxMode || config.codexApprovalPolicy || config.codexNetworkAccessEnabled !== void 0) {
|
|
91
|
+
const bits = [];
|
|
92
|
+
if (config.codexSandboxMode) bits.push(`sandbox=${config.codexSandboxMode}`);
|
|
93
|
+
if (config.codexApprovalPolicy) bits.push(`approval=${config.codexApprovalPolicy}`);
|
|
94
|
+
if (config.codexNetworkAccessEnabled !== void 0) bits.push(`network=${config.codexNetworkAccessEnabled}`);
|
|
95
|
+
console.log(`Codex defaults: ${bits.join(", ")}`);
|
|
96
|
+
}
|
|
48
97
|
|
|
49
98
|
// src/commands.ts
|
|
50
99
|
import {
|
|
@@ -56,10 +105,28 @@ function getCommandDefinitions() {
|
|
|
56
105
|
const session = new SlashCommandBuilder().setName("session").setDescription("Manage AI coding sessions").addSubcommand((sub) => sub.setName("new").setDescription("Create a new coding session").addStringOption((opt) => opt.setName("name").setDescription("Session name").setRequired(true)).addStringOption((opt) => opt.setName("provider").setDescription("AI provider").addChoices(
|
|
57
106
|
{ name: "Claude Code", value: "claude" },
|
|
58
107
|
{ name: "OpenAI Codex", value: "codex" }
|
|
59
|
-
)).addStringOption((opt) => opt.setName("
|
|
108
|
+
)).addStringOption((opt) => opt.setName("sandbox-mode").setDescription("Codex sandbox mode (Codex provider only)").addChoices(
|
|
109
|
+
{ name: "Read-only", value: "read-only" },
|
|
110
|
+
{ name: "Workspace write", value: "workspace-write" },
|
|
111
|
+
{ name: "Danger full access", value: "danger-full-access" }
|
|
112
|
+
)).addStringOption((opt) => opt.setName("approval-policy").setDescription("Codex approval policy (Codex provider only)").addChoices(
|
|
113
|
+
{ name: "Never ask", value: "never" },
|
|
114
|
+
{ name: "On request", value: "on-request" },
|
|
115
|
+
{ name: "On failure", value: "on-failure" },
|
|
116
|
+
{ name: "Untrusted", value: "untrusted" }
|
|
117
|
+
)).addBooleanOption((opt) => opt.setName("network-access").setDescription("Allow network in workspace-write sandbox (Codex only)")).addStringOption((opt) => opt.setName("directory").setDescription("Working directory (default: configured default)"))).addSubcommand((sub) => sub.setName("resume").setDescription("Resume an existing session from terminal").addStringOption((opt) => opt.setName("session-id").setDescription("Provider session ID").setRequired(true).setAutocomplete(true)).addStringOption((opt) => opt.setName("name").setDescription("Name for the Discord channel").setRequired(true)).addStringOption((opt) => opt.setName("provider").setDescription("AI provider").addChoices(
|
|
60
118
|
{ name: "Claude Code", value: "claude" },
|
|
61
119
|
{ name: "OpenAI Codex", value: "codex" }
|
|
62
|
-
)).addStringOption((opt) => opt.setName("
|
|
120
|
+
)).addStringOption((opt) => opt.setName("sandbox-mode").setDescription("Codex sandbox mode (Codex provider only)").addChoices(
|
|
121
|
+
{ name: "Read-only", value: "read-only" },
|
|
122
|
+
{ name: "Workspace write", value: "workspace-write" },
|
|
123
|
+
{ name: "Danger full access", value: "danger-full-access" }
|
|
124
|
+
)).addStringOption((opt) => opt.setName("approval-policy").setDescription("Codex approval policy (Codex provider only)").addChoices(
|
|
125
|
+
{ name: "Never ask", value: "never" },
|
|
126
|
+
{ name: "On request", value: "on-request" },
|
|
127
|
+
{ name: "On failure", value: "on-failure" },
|
|
128
|
+
{ name: "Untrusted", value: "untrusted" }
|
|
129
|
+
)).addBooleanOption((opt) => opt.setName("network-access").setDescription("Allow network in workspace-write sandbox (Codex only)")).addStringOption((opt) => opt.setName("directory").setDescription("Working directory (default: configured default)"))).addSubcommand((sub) => sub.setName("list").setDescription("List active sessions")).addSubcommand((sub) => sub.setName("end").setDescription("End the session in this channel")).addSubcommand((sub) => sub.setName("continue").setDescription("Continue the last conversation")).addSubcommand((sub) => sub.setName("stop").setDescription("Stop current generation")).addSubcommand((sub) => sub.setName("output").setDescription("Show recent conversation output").addIntegerOption((opt) => opt.setName("lines").setDescription("Number of lines (default 50)").setMinValue(1).setMaxValue(500))).addSubcommand((sub) => sub.setName("attach").setDescription("Show tmux attach command for terminal access")).addSubcommand((sub) => sub.setName("sync").setDescription("Reconnect orphaned sessions (tmux + provider channels)")).addSubcommand((sub) => sub.setName("model").setDescription("Change the model for this session").addStringOption((opt) => opt.setName("model").setDescription("Model name (e.g. claude-sonnet-4-5-20250929, gpt-5.3-codex)").setRequired(true))).addSubcommand((sub) => sub.setName("id").setDescription("Show the provider session ID for this channel")).addSubcommand((sub) => sub.setName("verbose").setDescription("Toggle showing tool calls and results in this session")).addSubcommand((sub) => sub.setName("mode").setDescription("Set session mode (auto/plan/normal)").addStringOption((opt) => opt.setName("mode").setDescription("Session mode").setRequired(true).addChoices(
|
|
63
130
|
{ name: "Auto \u2014 full autonomy", value: "auto" },
|
|
64
131
|
{ name: "Plan \u2014 plan before executing", value: "plan" },
|
|
65
132
|
{ name: "Normal \u2014 ask before destructive ops", value: "normal" }
|
|
@@ -366,7 +433,7 @@ function installPackageGlobally(pkg) {
|
|
|
366
433
|
async function loadCodexProvider() {
|
|
367
434
|
const pkg = PROVIDER_PACKAGES.codex;
|
|
368
435
|
try {
|
|
369
|
-
const { CodexProvider } = await import("./codex-provider-
|
|
436
|
+
const { CodexProvider } = await import("./codex-provider-BB2OO3OK.js");
|
|
370
437
|
providers.set("codex", new CodexProvider());
|
|
371
438
|
codexLoaded = true;
|
|
372
439
|
return;
|
|
@@ -382,7 +449,7 @@ async function loadCodexProvider() {
|
|
|
382
449
|
}
|
|
383
450
|
}
|
|
384
451
|
try {
|
|
385
|
-
const { CodexProvider } = await import("./codex-provider-
|
|
452
|
+
const { CodexProvider } = await import("./codex-provider-BB2OO3OK.js");
|
|
386
453
|
providers.set("codex", new CodexProvider());
|
|
387
454
|
codexLoaded = true;
|
|
388
455
|
} catch (err) {
|
|
@@ -759,6 +826,7 @@ var MODE_PROMPTS = {
|
|
|
759
826
|
var sessionStore = new Store("sessions.json");
|
|
760
827
|
var sessions = /* @__PURE__ */ new Map();
|
|
761
828
|
var channelToSession = /* @__PURE__ */ new Map();
|
|
829
|
+
var saveQueue = Promise.resolve();
|
|
762
830
|
function tmux(...args) {
|
|
763
831
|
return new Promise((resolve2, reject) => {
|
|
764
832
|
execFile("tmux", args, { encoding: "utf-8" }, (err, stdout) => {
|
|
@@ -775,10 +843,24 @@ async function tmuxSessionExists(tmuxName) {
|
|
|
775
843
|
return false;
|
|
776
844
|
}
|
|
777
845
|
}
|
|
846
|
+
function isPlaceholderChannelId(channelId) {
|
|
847
|
+
return !channelId || channelId === "pending";
|
|
848
|
+
}
|
|
778
849
|
async function loadSessions() {
|
|
779
850
|
const data = await sessionStore.read();
|
|
780
851
|
if (!data) return;
|
|
852
|
+
let cleaned = false;
|
|
781
853
|
for (const s of data) {
|
|
854
|
+
if (isPlaceholderChannelId(s.channelId)) {
|
|
855
|
+
cleaned = true;
|
|
856
|
+
console.warn(`Skipping invalid persisted session "${s.id}" (missing channel ID).`);
|
|
857
|
+
continue;
|
|
858
|
+
}
|
|
859
|
+
if (channelToSession.has(s.channelId)) {
|
|
860
|
+
cleaned = true;
|
|
861
|
+
console.warn(`Skipping duplicate persisted session "${s.id}" (channel ${s.channelId} already linked).`);
|
|
862
|
+
continue;
|
|
863
|
+
}
|
|
782
864
|
const provider = s.provider ?? "claude";
|
|
783
865
|
const providerSessionId = s.providerSessionId ?? s.claudeSessionId;
|
|
784
866
|
if (provider === "claude") {
|
|
@@ -801,11 +883,15 @@ async function loadSessions() {
|
|
|
801
883
|
});
|
|
802
884
|
channelToSession.set(s.channelId, s.id);
|
|
803
885
|
}
|
|
886
|
+
if (cleaned) {
|
|
887
|
+
await saveSessions();
|
|
888
|
+
}
|
|
804
889
|
console.log(`Restored ${sessions.size} session(s)`);
|
|
805
890
|
}
|
|
806
|
-
async function
|
|
891
|
+
async function persistSessionsNow() {
|
|
807
892
|
const data = [];
|
|
808
893
|
for (const [, s] of sessions) {
|
|
894
|
+
if (isPlaceholderChannelId(s.channelId)) continue;
|
|
809
895
|
data.push({
|
|
810
896
|
id: s.id,
|
|
811
897
|
channelId: s.channelId,
|
|
@@ -815,6 +901,9 @@ async function saveSessions() {
|
|
|
815
901
|
tmuxName: s.tmuxName,
|
|
816
902
|
providerSessionId: s.providerSessionId,
|
|
817
903
|
model: s.model,
|
|
904
|
+
sandboxMode: s.sandboxMode,
|
|
905
|
+
approvalPolicy: s.approvalPolicy,
|
|
906
|
+
networkAccessEnabled: s.networkAccessEnabled,
|
|
818
907
|
agentPersona: s.agentPersona,
|
|
819
908
|
verbose: s.verbose || void 0,
|
|
820
909
|
mode: s.mode !== "auto" ? s.mode : void 0,
|
|
@@ -826,8 +915,24 @@ async function saveSessions() {
|
|
|
826
915
|
}
|
|
827
916
|
await sessionStore.write(data);
|
|
828
917
|
}
|
|
829
|
-
|
|
918
|
+
function saveSessions() {
|
|
919
|
+
saveQueue = saveQueue.catch(() => {
|
|
920
|
+
}).then(async () => {
|
|
921
|
+
try {
|
|
922
|
+
await persistSessionsNow();
|
|
923
|
+
} catch (err) {
|
|
924
|
+
console.error(`Failed to persist sessions: ${err.message}`);
|
|
925
|
+
}
|
|
926
|
+
});
|
|
927
|
+
return saveQueue;
|
|
928
|
+
}
|
|
929
|
+
async function createSession(name, directory, channelId, projectName, provider = "claude", providerSessionId, options = {}) {
|
|
830
930
|
const resolvedDir = resolvePath(directory);
|
|
931
|
+
const effectiveOptions = provider === "codex" ? {
|
|
932
|
+
sandboxMode: options.sandboxMode ?? config.codexSandboxMode,
|
|
933
|
+
approvalPolicy: options.approvalPolicy ?? config.codexApprovalPolicy,
|
|
934
|
+
networkAccessEnabled: options.networkAccessEnabled ?? config.codexNetworkAccessEnabled
|
|
935
|
+
} : options;
|
|
831
936
|
if (!isPathAllowed(resolvedDir, config.allowedPaths)) {
|
|
832
937
|
throw new Error(`Directory not in allowed paths: ${resolvedDir}`);
|
|
833
938
|
}
|
|
@@ -838,14 +943,27 @@ async function createSession(name, directory, channelId, projectName, provider =
|
|
|
838
943
|
const usesTmux = providerInstance.supports("tmux");
|
|
839
944
|
let id = sanitizeSessionName(name);
|
|
840
945
|
let tmuxName = usesTmux ? `${SESSION_PREFIX}${id}` : "";
|
|
841
|
-
|
|
842
|
-
|
|
843
|
-
|
|
844
|
-
|
|
845
|
-
|
|
946
|
+
if (options.recoverExisting) {
|
|
947
|
+
if (sessions.has(id)) {
|
|
948
|
+
throw new Error(`Session "${id}" already exists`);
|
|
949
|
+
}
|
|
950
|
+
} else {
|
|
951
|
+
let suffix = 1;
|
|
952
|
+
while (sessions.has(id) || usesTmux && await tmuxSessionExists(tmuxName)) {
|
|
953
|
+
suffix++;
|
|
954
|
+
id = sanitizeSessionName(`${name}-${suffix}`);
|
|
955
|
+
if (usesTmux) tmuxName = `${SESSION_PREFIX}${id}`;
|
|
956
|
+
}
|
|
846
957
|
}
|
|
847
958
|
if (usesTmux) {
|
|
848
|
-
|
|
959
|
+
if (options.recoverExisting) {
|
|
960
|
+
const existing = await tmuxSessionExists(tmuxName);
|
|
961
|
+
if (!existing) {
|
|
962
|
+
await tmux("new-session", "-d", "-s", tmuxName, "-c", resolvedDir);
|
|
963
|
+
}
|
|
964
|
+
} else {
|
|
965
|
+
await tmux("new-session", "-d", "-s", tmuxName, "-c", resolvedDir);
|
|
966
|
+
}
|
|
849
967
|
}
|
|
850
968
|
const session = {
|
|
851
969
|
id,
|
|
@@ -855,6 +973,9 @@ async function createSession(name, directory, channelId, projectName, provider =
|
|
|
855
973
|
provider,
|
|
856
974
|
tmuxName,
|
|
857
975
|
providerSessionId,
|
|
976
|
+
sandboxMode: effectiveOptions.sandboxMode,
|
|
977
|
+
approvalPolicy: effectiveOptions.approvalPolicy,
|
|
978
|
+
networkAccessEnabled: effectiveOptions.networkAccessEnabled,
|
|
858
979
|
verbose: false,
|
|
859
980
|
mode: "auto",
|
|
860
981
|
isGenerating: false,
|
|
@@ -864,8 +985,10 @@ async function createSession(name, directory, channelId, projectName, provider =
|
|
|
864
985
|
totalCost: 0
|
|
865
986
|
};
|
|
866
987
|
sessions.set(id, session);
|
|
867
|
-
|
|
868
|
-
|
|
988
|
+
if (!isPlaceholderChannelId(channelId)) {
|
|
989
|
+
channelToSession.set(channelId, id);
|
|
990
|
+
await saveSessions();
|
|
991
|
+
}
|
|
869
992
|
return session;
|
|
870
993
|
}
|
|
871
994
|
function getSession(id) {
|
|
@@ -890,29 +1013,38 @@ async function endSession(id) {
|
|
|
890
1013
|
} catch {
|
|
891
1014
|
}
|
|
892
1015
|
}
|
|
893
|
-
|
|
1016
|
+
if (!isPlaceholderChannelId(session.channelId)) {
|
|
1017
|
+
channelToSession.delete(session.channelId);
|
|
1018
|
+
}
|
|
894
1019
|
sessions.delete(id);
|
|
895
1020
|
await saveSessions();
|
|
896
1021
|
}
|
|
897
|
-
function linkChannel(sessionId, channelId) {
|
|
1022
|
+
async function linkChannel(sessionId, channelId) {
|
|
898
1023
|
const session = sessions.get(sessionId);
|
|
899
|
-
if (session) {
|
|
1024
|
+
if (!session) {
|
|
1025
|
+
throw new Error(`Session "${sessionId}" not found`);
|
|
1026
|
+
}
|
|
1027
|
+
if (!isPlaceholderChannelId(session.channelId)) {
|
|
900
1028
|
channelToSession.delete(session.channelId);
|
|
901
|
-
session.channelId = channelId;
|
|
902
|
-
channelToSession.set(channelId, sessionId);
|
|
903
|
-
saveSessions();
|
|
904
1029
|
}
|
|
1030
|
+
session.channelId = channelId;
|
|
1031
|
+
channelToSession.set(channelId, sessionId);
|
|
1032
|
+
await saveSessions();
|
|
905
1033
|
}
|
|
906
|
-
function unlinkChannel(channelId) {
|
|
907
|
-
|
|
908
|
-
if (sessionId) {
|
|
909
|
-
|
|
910
|
-
|
|
911
|
-
|
|
912
|
-
|
|
1034
|
+
async function unlinkChannel(channelId) {
|
|
1035
|
+
let sessionId = channelToSession.get(channelId);
|
|
1036
|
+
if (!sessionId) {
|
|
1037
|
+
for (const [id, session] of sessions) {
|
|
1038
|
+
if (session.channelId === channelId) {
|
|
1039
|
+
sessionId = id;
|
|
1040
|
+
break;
|
|
1041
|
+
}
|
|
913
1042
|
}
|
|
914
|
-
saveSessions();
|
|
915
1043
|
}
|
|
1044
|
+
if (!sessionId) return;
|
|
1045
|
+
channelToSession.delete(channelId);
|
|
1046
|
+
sessions.delete(sessionId);
|
|
1047
|
+
await saveSessions();
|
|
916
1048
|
}
|
|
917
1049
|
function setModel(sessionId, model) {
|
|
918
1050
|
const session = sessions.get(sessionId);
|
|
@@ -969,6 +1101,9 @@ async function* sendPrompt(sessionId, prompt) {
|
|
|
969
1101
|
directory: session.directory,
|
|
970
1102
|
providerSessionId: session.providerSessionId,
|
|
971
1103
|
model: session.model,
|
|
1104
|
+
sandboxMode: session.sandboxMode,
|
|
1105
|
+
approvalPolicy: session.approvalPolicy,
|
|
1106
|
+
networkAccessEnabled: session.networkAccessEnabled,
|
|
972
1107
|
systemPromptParts,
|
|
973
1108
|
abortController: controller
|
|
974
1109
|
});
|
|
@@ -1010,6 +1145,9 @@ async function* continueSession(sessionId) {
|
|
|
1010
1145
|
directory: session.directory,
|
|
1011
1146
|
providerSessionId: session.providerSessionId,
|
|
1012
1147
|
model: session.model,
|
|
1148
|
+
sandboxMode: session.sandboxMode,
|
|
1149
|
+
approvalPolicy: session.approvalPolicy,
|
|
1150
|
+
networkAccessEnabled: session.networkAccessEnabled,
|
|
1013
1151
|
systemPromptParts,
|
|
1014
1152
|
abortController: controller
|
|
1015
1153
|
});
|
|
@@ -1742,6 +1880,17 @@ ${event.message}
|
|
|
1742
1880
|
break;
|
|
1743
1881
|
}
|
|
1744
1882
|
case "session_init": {
|
|
1883
|
+
const session = getSession(sessionId);
|
|
1884
|
+
const providerSessionId = event.providerSessionId || session?.providerSessionId;
|
|
1885
|
+
if (providerSessionId) {
|
|
1886
|
+
const currentTopic = channel.topic ?? "";
|
|
1887
|
+
const topicBase = currentTopic ? currentTopic.replace(/\s*\|\s*Provider Session:\s*[^\s|]+/i, "") : `${session?.provider === "codex" ? "OpenAI Codex" : "Claude Code"} session | Dir: ${session?.directory || "unknown"}`;
|
|
1888
|
+
const nextTopic = truncate(`${topicBase} | Provider Session: ${providerSessionId}`, 1024);
|
|
1889
|
+
if (nextTopic !== currentTopic) {
|
|
1890
|
+
await channel.setTopic(nextTopic).catch(() => {
|
|
1891
|
+
});
|
|
1892
|
+
}
|
|
1893
|
+
}
|
|
1745
1894
|
break;
|
|
1746
1895
|
}
|
|
1747
1896
|
}
|
|
@@ -1929,9 +2078,38 @@ var PROVIDER_COLORS = {
|
|
|
1929
2078
|
claude: 3447003,
|
|
1930
2079
|
codex: 1090431
|
|
1931
2080
|
};
|
|
2081
|
+
function resolveCodexSessionOptions(interaction, provider) {
|
|
2082
|
+
if (provider !== "codex") return {};
|
|
2083
|
+
const sandboxMode = interaction.options.getString("sandbox-mode") ?? config.codexSandboxMode;
|
|
2084
|
+
const approvalPolicy = interaction.options.getString("approval-policy") ?? config.codexApprovalPolicy;
|
|
2085
|
+
const networkAccessEnabled = interaction.options.getBoolean("network-access") ?? config.codexNetworkAccessEnabled;
|
|
2086
|
+
return { sandboxMode, approvalPolicy, networkAccessEnabled };
|
|
2087
|
+
}
|
|
2088
|
+
function addCodexPolicyFields(fields, options) {
|
|
2089
|
+
if (options.sandboxMode) {
|
|
2090
|
+
fields.push({ name: "Sandbox", value: options.sandboxMode, inline: true });
|
|
2091
|
+
}
|
|
2092
|
+
if (options.approvalPolicy) {
|
|
2093
|
+
fields.push({ name: "Approval", value: options.approvalPolicy, inline: true });
|
|
2094
|
+
}
|
|
2095
|
+
if (options.networkAccessEnabled !== void 0) {
|
|
2096
|
+
fields.push({ name: "Network Access", value: options.networkAccessEnabled ? "enabled" : "disabled", inline: true });
|
|
2097
|
+
}
|
|
2098
|
+
}
|
|
2099
|
+
function parseTopicDirectory(topic) {
|
|
2100
|
+
if (!topic) return null;
|
|
2101
|
+
const m = topic.match(/\bDir:\s*(.+?)(?:\s*\|\s*Provider Session:|$)/i);
|
|
2102
|
+
return m?.[1]?.trim() || null;
|
|
2103
|
+
}
|
|
2104
|
+
function parseTopicProviderSessionId(topic) {
|
|
2105
|
+
if (!topic) return void 0;
|
|
2106
|
+
const m = topic.match(/\bProvider Session:\s*([^\s|]+)/i);
|
|
2107
|
+
return m?.[1]?.trim() || void 0;
|
|
2108
|
+
}
|
|
1932
2109
|
async function handleSessionNew(interaction) {
|
|
1933
2110
|
const name = interaction.options.getString("name", true);
|
|
1934
2111
|
const provider = interaction.options.getString("provider") || "claude";
|
|
2112
|
+
const codexOptions = resolveCodexSessionOptions(interaction, provider);
|
|
1935
2113
|
let directory = interaction.options.getString("directory");
|
|
1936
2114
|
if (!directory) {
|
|
1937
2115
|
const parentId = interaction.channel?.parentId;
|
|
@@ -1943,18 +2121,19 @@ async function handleSessionNew(interaction) {
|
|
|
1943
2121
|
}
|
|
1944
2122
|
await interaction.deferReply();
|
|
1945
2123
|
let channel;
|
|
2124
|
+
let session;
|
|
1946
2125
|
try {
|
|
1947
2126
|
const guild = interaction.guild;
|
|
1948
2127
|
const projectName = projectNameFromDir(directory);
|
|
1949
2128
|
const { category } = await ensureProjectCategory(guild, projectName, directory);
|
|
1950
|
-
|
|
2129
|
+
session = await createSession(name, directory, "pending", projectName, provider, void 0, codexOptions);
|
|
1951
2130
|
channel = await guild.channels.create({
|
|
1952
2131
|
name: `${provider}-${session.id}`,
|
|
1953
2132
|
type: ChannelType.GuildText,
|
|
1954
2133
|
parent: category.id,
|
|
1955
2134
|
topic: `${PROVIDER_LABELS[provider]} session | Dir: ${directory}`
|
|
1956
2135
|
});
|
|
1957
|
-
linkChannel(session.id, channel.id);
|
|
2136
|
+
await linkChannel(session.id, channel.id);
|
|
1958
2137
|
const fields = [
|
|
1959
2138
|
{ name: "Channel", value: `#${provider}-${session.id}`, inline: true },
|
|
1960
2139
|
{ name: "Provider", value: PROVIDER_LABELS[provider], inline: true },
|
|
@@ -1964,6 +2143,7 @@ async function handleSessionNew(interaction) {
|
|
|
1964
2143
|
if (session.tmuxName) {
|
|
1965
2144
|
fields.push({ name: "Terminal", value: `\`tmux attach -t ${session.tmuxName}\``, inline: false });
|
|
1966
2145
|
}
|
|
2146
|
+
addCodexPolicyFields(fields, codexOptions);
|
|
1967
2147
|
const embed = new EmbedBuilder3().setColor(3066993).setTitle(`Session Created: ${session.id}`).addFields(fields);
|
|
1968
2148
|
await interaction.editReply({ embeds: [embed] });
|
|
1969
2149
|
log(`Session "${session.id}" (${provider}) created by ${interaction.user.tag} in ${directory}`);
|
|
@@ -1974,6 +2154,7 @@ async function handleSessionNew(interaction) {
|
|
|
1974
2154
|
if (session.tmuxName) {
|
|
1975
2155
|
welcomeFields.push({ name: "Terminal Access", value: `\`tmux attach -t ${session.tmuxName}\``, inline: false });
|
|
1976
2156
|
}
|
|
2157
|
+
addCodexPolicyFields(welcomeFields, codexOptions);
|
|
1977
2158
|
welcomeEmbed.addFields(welcomeFields);
|
|
1978
2159
|
await channel.send({ embeds: [welcomeEmbed] });
|
|
1979
2160
|
} catch (err) {
|
|
@@ -1983,6 +2164,12 @@ async function handleSessionNew(interaction) {
|
|
|
1983
2164
|
} catch {
|
|
1984
2165
|
}
|
|
1985
2166
|
}
|
|
2167
|
+
if (session) {
|
|
2168
|
+
try {
|
|
2169
|
+
await endSession(session.id);
|
|
2170
|
+
} catch {
|
|
2171
|
+
}
|
|
2172
|
+
}
|
|
1986
2173
|
await interaction.editReply(`Failed to create session: ${err.message}`);
|
|
1987
2174
|
}
|
|
1988
2175
|
}
|
|
@@ -2094,6 +2281,7 @@ async function handleSessionResume(interaction) {
|
|
|
2094
2281
|
const providerSessionId = interaction.options.getString("session-id", true);
|
|
2095
2282
|
const name = interaction.options.getString("name", true);
|
|
2096
2283
|
const provider = interaction.options.getString("provider") || "claude";
|
|
2284
|
+
const codexOptions = resolveCodexSessionOptions(interaction, provider);
|
|
2097
2285
|
const directory = interaction.options.getString("directory") || config.defaultDirectory;
|
|
2098
2286
|
if (provider === "claude") {
|
|
2099
2287
|
const uuidRegex = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
|
|
@@ -2107,18 +2295,27 @@ async function handleSessionResume(interaction) {
|
|
|
2107
2295
|
}
|
|
2108
2296
|
await interaction.deferReply();
|
|
2109
2297
|
let channel;
|
|
2298
|
+
let session;
|
|
2110
2299
|
try {
|
|
2111
2300
|
const guild = interaction.guild;
|
|
2112
2301
|
const projectName = projectNameFromDir(directory);
|
|
2113
2302
|
const { category } = await ensureProjectCategory(guild, projectName, directory);
|
|
2114
|
-
|
|
2303
|
+
session = await createSession(
|
|
2304
|
+
name,
|
|
2305
|
+
directory,
|
|
2306
|
+
"pending",
|
|
2307
|
+
projectName,
|
|
2308
|
+
provider,
|
|
2309
|
+
providerSessionId,
|
|
2310
|
+
codexOptions
|
|
2311
|
+
);
|
|
2115
2312
|
channel = await guild.channels.create({
|
|
2116
2313
|
name: `${provider}-${session.id}`,
|
|
2117
2314
|
type: ChannelType.GuildText,
|
|
2118
2315
|
parent: category.id,
|
|
2119
|
-
topic: `${PROVIDER_LABELS[provider]} session (resumed) | Dir: ${directory}`
|
|
2316
|
+
topic: `${PROVIDER_LABELS[provider]} session (resumed) | Dir: ${directory} | Provider Session: ${providerSessionId}`
|
|
2120
2317
|
});
|
|
2121
|
-
linkChannel(session.id, channel.id);
|
|
2318
|
+
await linkChannel(session.id, channel.id);
|
|
2122
2319
|
const fields = [
|
|
2123
2320
|
{ name: "Channel", value: `#${provider}-${session.id}`, inline: true },
|
|
2124
2321
|
{ name: "Provider", value: PROVIDER_LABELS[provider], inline: true },
|
|
@@ -2129,6 +2326,7 @@ async function handleSessionResume(interaction) {
|
|
|
2129
2326
|
if (session.tmuxName) {
|
|
2130
2327
|
fields.push({ name: "Terminal", value: `\`tmux attach -t ${session.tmuxName}\``, inline: false });
|
|
2131
2328
|
}
|
|
2329
|
+
addCodexPolicyFields(fields, codexOptions);
|
|
2132
2330
|
const embed = new EmbedBuilder3().setColor(15105570).setTitle(`Session Resumed: ${session.id}`).addFields(fields);
|
|
2133
2331
|
await interaction.editReply({ embeds: [embed] });
|
|
2134
2332
|
log(`Session "${session.id}" (${provider}, resumed ${providerSessionId}) created by ${interaction.user.tag} in ${directory}`);
|
|
@@ -2139,6 +2337,7 @@ async function handleSessionResume(interaction) {
|
|
|
2139
2337
|
if (session.tmuxName) {
|
|
2140
2338
|
welcomeFields.push({ name: "Terminal Access", value: `\`tmux attach -t ${session.tmuxName}\``, inline: false });
|
|
2141
2339
|
}
|
|
2340
|
+
addCodexPolicyFields(welcomeFields, codexOptions);
|
|
2142
2341
|
await channel.send({
|
|
2143
2342
|
embeds: [
|
|
2144
2343
|
new EmbedBuilder3().setColor(15105570).setTitle(`${PROVIDER_LABELS[provider]} Session (Resumed)`).setDescription(
|
|
@@ -2153,6 +2352,12 @@ async function handleSessionResume(interaction) {
|
|
|
2153
2352
|
} catch {
|
|
2154
2353
|
}
|
|
2155
2354
|
}
|
|
2355
|
+
if (session) {
|
|
2356
|
+
try {
|
|
2357
|
+
await endSession(session.id);
|
|
2358
|
+
} catch {
|
|
2359
|
+
}
|
|
2360
|
+
}
|
|
2156
2361
|
await interaction.editReply(`Failed to resume session: ${err.message}`);
|
|
2157
2362
|
}
|
|
2158
2363
|
}
|
|
@@ -2174,7 +2379,13 @@ async function handleSessionList(interaction) {
|
|
|
2174
2379
|
const status = s.isGenerating ? "\u{1F7E2} generating" : "\u26AA idle";
|
|
2175
2380
|
const modeEmoji = { auto: "\u26A1", plan: "\u{1F4CB}", normal: "\u{1F6E1}\uFE0F" }[s.mode] || "\u26A1";
|
|
2176
2381
|
const providerTag = `[${s.provider}]`;
|
|
2177
|
-
|
|
2382
|
+
const codexPolicy = s.provider === "codex" ? [
|
|
2383
|
+
s.sandboxMode ? `sandbox:${s.sandboxMode}` : "",
|
|
2384
|
+
s.approvalPolicy ? `approval:${s.approvalPolicy}` : "",
|
|
2385
|
+
s.networkAccessEnabled !== void 0 ? `network:${s.networkAccessEnabled ? "on" : "off"}` : ""
|
|
2386
|
+
].filter(Boolean).join(" ") : "";
|
|
2387
|
+
const policySuffix = codexPolicy ? ` | ${codexPolicy}` : "";
|
|
2388
|
+
return `**${s.id}** ${providerTag} \u2014 ${status} ${modeEmoji} ${s.mode} | ${formatUptime(s.createdAt)} uptime | ${s.messageCount} msgs | $${s.totalCost.toFixed(4)} | ${formatLastActivity(s.lastActivity)}${policySuffix}`;
|
|
2178
2389
|
});
|
|
2179
2390
|
embed.addFields({ name: `\u{1F4C1} ${project}`, value: lines.join("\n") });
|
|
2180
2391
|
}
|
|
@@ -2281,7 +2492,38 @@ async function handleSessionSync(interaction) {
|
|
|
2281
2492
|
const tmuxSessions = await listTmuxSessions();
|
|
2282
2493
|
const currentSessions = getAllSessions();
|
|
2283
2494
|
const currentIds = new Set(currentSessions.map((s) => s.id));
|
|
2284
|
-
|
|
2495
|
+
const currentChannelIds = new Set(currentSessions.map((s) => s.channelId));
|
|
2496
|
+
let syncedTmux = 0;
|
|
2497
|
+
let syncedChannels = 0;
|
|
2498
|
+
for (const ch of guild.channels.cache.values()) {
|
|
2499
|
+
if (ch.type !== ChannelType.GuildText) continue;
|
|
2500
|
+
if (currentChannelIds.has(ch.id)) continue;
|
|
2501
|
+
const m = ch.name.match(/^(claude|codex)-(.+)$/);
|
|
2502
|
+
if (!m) continue;
|
|
2503
|
+
const provider = m[1];
|
|
2504
|
+
const sessionName = m[2];
|
|
2505
|
+
const directory = parseTopicDirectory(ch.topic) || config.defaultDirectory;
|
|
2506
|
+
const providerSessionId = parseTopicProviderSessionId(ch.topic);
|
|
2507
|
+
const projectName = projectNameFromDir(directory);
|
|
2508
|
+
if (ch.parentId) {
|
|
2509
|
+
getOrCreateProject(projectName, directory, ch.parentId);
|
|
2510
|
+
}
|
|
2511
|
+
try {
|
|
2512
|
+
const recovered = await createSession(
|
|
2513
|
+
sessionName,
|
|
2514
|
+
directory,
|
|
2515
|
+
ch.id,
|
|
2516
|
+
projectName,
|
|
2517
|
+
provider,
|
|
2518
|
+
providerSessionId,
|
|
2519
|
+
{ recoverExisting: true }
|
|
2520
|
+
);
|
|
2521
|
+
syncedChannels++;
|
|
2522
|
+
currentIds.add(recovered.id);
|
|
2523
|
+
currentChannelIds.add(ch.id);
|
|
2524
|
+
} catch {
|
|
2525
|
+
}
|
|
2526
|
+
}
|
|
2285
2527
|
for (const tmuxSession of tmuxSessions) {
|
|
2286
2528
|
if (currentIds.has(tmuxSession.id)) continue;
|
|
2287
2529
|
const projectName = projectNameFromDir(tmuxSession.directory);
|
|
@@ -2292,11 +2534,25 @@ async function handleSessionSync(interaction) {
|
|
|
2292
2534
|
parent: category.id,
|
|
2293
2535
|
topic: `Claude Code session (synced) | Dir: ${tmuxSession.directory}`
|
|
2294
2536
|
});
|
|
2295
|
-
await createSession(
|
|
2296
|
-
|
|
2297
|
-
|
|
2537
|
+
const recovered = await createSession(
|
|
2538
|
+
tmuxSession.id,
|
|
2539
|
+
tmuxSession.directory,
|
|
2540
|
+
channel.id,
|
|
2541
|
+
projectName,
|
|
2542
|
+
"claude",
|
|
2543
|
+
void 0,
|
|
2544
|
+
{ recoverExisting: true }
|
|
2545
|
+
);
|
|
2546
|
+
syncedTmux++;
|
|
2547
|
+
currentIds.add(recovered.id);
|
|
2548
|
+
currentChannelIds.add(channel.id);
|
|
2549
|
+
}
|
|
2550
|
+
const synced = syncedChannels + syncedTmux;
|
|
2551
|
+
const detail = [];
|
|
2552
|
+
if (syncedChannels > 0) detail.push(`${syncedChannels} channel`);
|
|
2553
|
+
if (syncedTmux > 0) detail.push(`${syncedTmux} tmux`);
|
|
2298
2554
|
await interaction.editReply(
|
|
2299
|
-
synced > 0 ? `Synced ${synced} orphaned session(s).` : "No orphaned sessions found."
|
|
2555
|
+
synced > 0 ? `Synced ${synced} orphaned session(s) (${detail.join(", ")}).` : "No orphaned sessions found."
|
|
2300
2556
|
);
|
|
2301
2557
|
}
|
|
2302
2558
|
async function handleSessionId(interaction) {
|
|
@@ -3530,7 +3786,7 @@ async function startBot() {
|
|
|
3530
3786
|
client.on("messageCreate", handleMessage);
|
|
3531
3787
|
client.on("channelDelete", (channel) => {
|
|
3532
3788
|
if (channel.type === ChannelType2.GuildText) {
|
|
3533
|
-
unlinkChannel(channel.id);
|
|
3789
|
+
void unlinkChannel(channel.id);
|
|
3534
3790
|
}
|
|
3535
3791
|
});
|
|
3536
3792
|
client.once("ready", async () => {
|
package/dist/cli.js
CHANGED
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
var command = process.argv[2];
|
|
5
5
|
switch (command) {
|
|
6
6
|
case "setup": {
|
|
7
|
-
const { runSetup } = await import("./setup-
|
|
7
|
+
const { runSetup } = await import("./setup-VHXX7YM6.js");
|
|
8
8
|
await runSetup();
|
|
9
9
|
break;
|
|
10
10
|
}
|
|
@@ -18,7 +18,7 @@ switch (command) {
|
|
|
18
18
|
console.log("Run \x1B[36magentcord setup\x1B[0m to configure.\n");
|
|
19
19
|
process.exit(1);
|
|
20
20
|
}
|
|
21
|
-
const { startBot } = await import("./bot-
|
|
21
|
+
const { startBot } = await import("./bot-QXUF3LNK.js");
|
|
22
22
|
console.log("agentcord starting...");
|
|
23
23
|
await startBot();
|
|
24
24
|
break;
|
|
@@ -96,6 +96,11 @@ var CodexProvider = class {
|
|
|
96
96
|
skipGitRepoCheck: true
|
|
97
97
|
};
|
|
98
98
|
if (options.model) threadOptions.model = options.model;
|
|
99
|
+
if (options.sandboxMode) threadOptions.sandboxMode = options.sandboxMode;
|
|
100
|
+
if (options.approvalPolicy) threadOptions.approvalPolicy = options.approvalPolicy;
|
|
101
|
+
if (options.networkAccessEnabled !== void 0) {
|
|
102
|
+
threadOptions.networkAccessEnabled = options.networkAccessEnabled;
|
|
103
|
+
}
|
|
99
104
|
const thread = options.providerSessionId ? codex.resumeThread(options.providerSessionId, threadOptions) : codex.startThread(threadOptions);
|
|
100
105
|
const { events } = await thread.runStreamed(input);
|
|
101
106
|
yield* this.translateEvents(events, options.abortController);
|
|
@@ -118,6 +123,11 @@ var CodexProvider = class {
|
|
|
118
123
|
skipGitRepoCheck: true
|
|
119
124
|
};
|
|
120
125
|
if (options.model) threadOptions.model = options.model;
|
|
126
|
+
if (options.sandboxMode) threadOptions.sandboxMode = options.sandboxMode;
|
|
127
|
+
if (options.approvalPolicy) threadOptions.approvalPolicy = options.approvalPolicy;
|
|
128
|
+
if (options.networkAccessEnabled !== void 0) {
|
|
129
|
+
threadOptions.networkAccessEnabled = options.networkAccessEnabled;
|
|
130
|
+
}
|
|
121
131
|
const thread = codex.resumeThread(options.providerSessionId, threadOptions);
|
|
122
132
|
const { events } = await thread.runStreamed("Continue from where you left off.");
|
|
123
133
|
yield* this.translateEvents(events, options.abortController);
|
|
@@ -40,6 +40,7 @@ function writeEnvFile(env) {
|
|
|
40
40
|
{ comment: "# Discord App", keys: ["DISCORD_TOKEN", "DISCORD_CLIENT_ID", "DISCORD_GUILD_ID"] },
|
|
41
41
|
{ comment: "# Security", keys: ["ALLOWED_USERS", "ALLOW_ALL_USERS"] },
|
|
42
42
|
{ comment: "# Paths", keys: ["ALLOWED_PATHS", "DEFAULT_DIRECTORY"] },
|
|
43
|
+
{ comment: "# Codex Defaults", keys: ["CODEX_SANDBOX_MODE", "CODEX_APPROVAL_POLICY", "CODEX_NETWORK_ACCESS_ENABLED"] },
|
|
43
44
|
{ comment: "# Optional", keys: ["MESSAGE_RETENTION_DAYS", "RATE_LIMIT_MS"] }
|
|
44
45
|
];
|
|
45
46
|
for (const section of sections) {
|
|
@@ -237,6 +238,9 @@ async function runSetup() {
|
|
|
237
238
|
if (allowedPaths) env.ALLOWED_PATHS = allowedPaths;
|
|
238
239
|
if (existing.MESSAGE_RETENTION_DAYS) env.MESSAGE_RETENTION_DAYS = existing.MESSAGE_RETENTION_DAYS;
|
|
239
240
|
if (existing.RATE_LIMIT_MS) env.RATE_LIMIT_MS = existing.RATE_LIMIT_MS;
|
|
241
|
+
if (existing.CODEX_SANDBOX_MODE) env.CODEX_SANDBOX_MODE = existing.CODEX_SANDBOX_MODE;
|
|
242
|
+
if (existing.CODEX_APPROVAL_POLICY) env.CODEX_APPROVAL_POLICY = existing.CODEX_APPROVAL_POLICY;
|
|
243
|
+
if (existing.CODEX_NETWORK_ACCESS_ENABLED) env.CODEX_NETWORK_ACCESS_ENABLED = existing.CODEX_NETWORK_ACCESS_ENABLED;
|
|
240
244
|
const s = p.spinner();
|
|
241
245
|
s.start("Writing .env file");
|
|
242
246
|
writeEnvFile(env);
|