agentcord 0.2.0 → 2.1.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/.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
|
@@ -118,8 +118,15 @@ ALLOWED_USERS=123456789,987654321 # Comma-separated user IDs
|
|
|
118
118
|
ALLOW_ALL_USERS=false # Or true to skip whitelist
|
|
119
119
|
ALLOWED_PATHS=/Users/me/Dev # Restrict accessible directories
|
|
120
120
|
DEFAULT_DIRECTORY=/Users/me/Dev # Default for new sessions
|
|
121
|
+
CODEX_SANDBOX_MODE=workspace-write # read-only | workspace-write | danger-full-access
|
|
122
|
+
CODEX_APPROVAL_POLICY=on-request # never | on-request | on-failure | untrusted
|
|
123
|
+
CODEX_NETWORK_ACCESS_ENABLED=true # true | false
|
|
121
124
|
```
|
|
122
125
|
|
|
126
|
+
You can also override Codex policy per session when creating/resuming via:
|
|
127
|
+
- `/session new ... sandbox-mode:<mode> approval-policy:<policy> network-access:<bool>`
|
|
128
|
+
- `/session resume ... sandbox-mode:<mode> approval-policy:<policy> network-access:<bool>`
|
|
129
|
+
|
|
123
130
|
## Development
|
|
124
131
|
|
|
125
132
|
```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 tmux sessions")).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
|
}
|
|
@@ -855,6 +960,9 @@ async function createSession(name, directory, channelId, projectName, provider =
|
|
|
855
960
|
provider,
|
|
856
961
|
tmuxName,
|
|
857
962
|
providerSessionId,
|
|
963
|
+
sandboxMode: effectiveOptions.sandboxMode,
|
|
964
|
+
approvalPolicy: effectiveOptions.approvalPolicy,
|
|
965
|
+
networkAccessEnabled: effectiveOptions.networkAccessEnabled,
|
|
858
966
|
verbose: false,
|
|
859
967
|
mode: "auto",
|
|
860
968
|
isGenerating: false,
|
|
@@ -864,8 +972,10 @@ async function createSession(name, directory, channelId, projectName, provider =
|
|
|
864
972
|
totalCost: 0
|
|
865
973
|
};
|
|
866
974
|
sessions.set(id, session);
|
|
867
|
-
|
|
868
|
-
|
|
975
|
+
if (!isPlaceholderChannelId(channelId)) {
|
|
976
|
+
channelToSession.set(channelId, id);
|
|
977
|
+
await saveSessions();
|
|
978
|
+
}
|
|
869
979
|
return session;
|
|
870
980
|
}
|
|
871
981
|
function getSession(id) {
|
|
@@ -890,29 +1000,38 @@ async function endSession(id) {
|
|
|
890
1000
|
} catch {
|
|
891
1001
|
}
|
|
892
1002
|
}
|
|
893
|
-
|
|
1003
|
+
if (!isPlaceholderChannelId(session.channelId)) {
|
|
1004
|
+
channelToSession.delete(session.channelId);
|
|
1005
|
+
}
|
|
894
1006
|
sessions.delete(id);
|
|
895
1007
|
await saveSessions();
|
|
896
1008
|
}
|
|
897
|
-
function linkChannel(sessionId, channelId) {
|
|
1009
|
+
async function linkChannel(sessionId, channelId) {
|
|
898
1010
|
const session = sessions.get(sessionId);
|
|
899
|
-
if (session) {
|
|
1011
|
+
if (!session) {
|
|
1012
|
+
throw new Error(`Session "${sessionId}" not found`);
|
|
1013
|
+
}
|
|
1014
|
+
if (!isPlaceholderChannelId(session.channelId)) {
|
|
900
1015
|
channelToSession.delete(session.channelId);
|
|
901
|
-
session.channelId = channelId;
|
|
902
|
-
channelToSession.set(channelId, sessionId);
|
|
903
|
-
saveSessions();
|
|
904
1016
|
}
|
|
1017
|
+
session.channelId = channelId;
|
|
1018
|
+
channelToSession.set(channelId, sessionId);
|
|
1019
|
+
await saveSessions();
|
|
905
1020
|
}
|
|
906
|
-
function unlinkChannel(channelId) {
|
|
907
|
-
|
|
908
|
-
if (sessionId) {
|
|
909
|
-
|
|
910
|
-
|
|
911
|
-
|
|
912
|
-
|
|
1021
|
+
async function unlinkChannel(channelId) {
|
|
1022
|
+
let sessionId = channelToSession.get(channelId);
|
|
1023
|
+
if (!sessionId) {
|
|
1024
|
+
for (const [id, session] of sessions) {
|
|
1025
|
+
if (session.channelId === channelId) {
|
|
1026
|
+
sessionId = id;
|
|
1027
|
+
break;
|
|
1028
|
+
}
|
|
913
1029
|
}
|
|
914
|
-
saveSessions();
|
|
915
1030
|
}
|
|
1031
|
+
if (!sessionId) return;
|
|
1032
|
+
channelToSession.delete(channelId);
|
|
1033
|
+
sessions.delete(sessionId);
|
|
1034
|
+
await saveSessions();
|
|
916
1035
|
}
|
|
917
1036
|
function setModel(sessionId, model) {
|
|
918
1037
|
const session = sessions.get(sessionId);
|
|
@@ -969,6 +1088,9 @@ async function* sendPrompt(sessionId, prompt) {
|
|
|
969
1088
|
directory: session.directory,
|
|
970
1089
|
providerSessionId: session.providerSessionId,
|
|
971
1090
|
model: session.model,
|
|
1091
|
+
sandboxMode: session.sandboxMode,
|
|
1092
|
+
approvalPolicy: session.approvalPolicy,
|
|
1093
|
+
networkAccessEnabled: session.networkAccessEnabled,
|
|
972
1094
|
systemPromptParts,
|
|
973
1095
|
abortController: controller
|
|
974
1096
|
});
|
|
@@ -1010,6 +1132,9 @@ async function* continueSession(sessionId) {
|
|
|
1010
1132
|
directory: session.directory,
|
|
1011
1133
|
providerSessionId: session.providerSessionId,
|
|
1012
1134
|
model: session.model,
|
|
1135
|
+
sandboxMode: session.sandboxMode,
|
|
1136
|
+
approvalPolicy: session.approvalPolicy,
|
|
1137
|
+
networkAccessEnabled: session.networkAccessEnabled,
|
|
1013
1138
|
systemPromptParts,
|
|
1014
1139
|
abortController: controller
|
|
1015
1140
|
});
|
|
@@ -1929,9 +2054,28 @@ var PROVIDER_COLORS = {
|
|
|
1929
2054
|
claude: 3447003,
|
|
1930
2055
|
codex: 1090431
|
|
1931
2056
|
};
|
|
2057
|
+
function resolveCodexSessionOptions(interaction, provider) {
|
|
2058
|
+
if (provider !== "codex") return {};
|
|
2059
|
+
const sandboxMode = interaction.options.getString("sandbox-mode") ?? config.codexSandboxMode;
|
|
2060
|
+
const approvalPolicy = interaction.options.getString("approval-policy") ?? config.codexApprovalPolicy;
|
|
2061
|
+
const networkAccessEnabled = interaction.options.getBoolean("network-access") ?? config.codexNetworkAccessEnabled;
|
|
2062
|
+
return { sandboxMode, approvalPolicy, networkAccessEnabled };
|
|
2063
|
+
}
|
|
2064
|
+
function addCodexPolicyFields(fields, options) {
|
|
2065
|
+
if (options.sandboxMode) {
|
|
2066
|
+
fields.push({ name: "Sandbox", value: options.sandboxMode, inline: true });
|
|
2067
|
+
}
|
|
2068
|
+
if (options.approvalPolicy) {
|
|
2069
|
+
fields.push({ name: "Approval", value: options.approvalPolicy, inline: true });
|
|
2070
|
+
}
|
|
2071
|
+
if (options.networkAccessEnabled !== void 0) {
|
|
2072
|
+
fields.push({ name: "Network Access", value: options.networkAccessEnabled ? "enabled" : "disabled", inline: true });
|
|
2073
|
+
}
|
|
2074
|
+
}
|
|
1932
2075
|
async function handleSessionNew(interaction) {
|
|
1933
2076
|
const name = interaction.options.getString("name", true);
|
|
1934
2077
|
const provider = interaction.options.getString("provider") || "claude";
|
|
2078
|
+
const codexOptions = resolveCodexSessionOptions(interaction, provider);
|
|
1935
2079
|
let directory = interaction.options.getString("directory");
|
|
1936
2080
|
if (!directory) {
|
|
1937
2081
|
const parentId = interaction.channel?.parentId;
|
|
@@ -1943,18 +2087,19 @@ async function handleSessionNew(interaction) {
|
|
|
1943
2087
|
}
|
|
1944
2088
|
await interaction.deferReply();
|
|
1945
2089
|
let channel;
|
|
2090
|
+
let session;
|
|
1946
2091
|
try {
|
|
1947
2092
|
const guild = interaction.guild;
|
|
1948
2093
|
const projectName = projectNameFromDir(directory);
|
|
1949
2094
|
const { category } = await ensureProjectCategory(guild, projectName, directory);
|
|
1950
|
-
|
|
2095
|
+
session = await createSession(name, directory, "pending", projectName, provider, void 0, codexOptions);
|
|
1951
2096
|
channel = await guild.channels.create({
|
|
1952
2097
|
name: `${provider}-${session.id}`,
|
|
1953
2098
|
type: ChannelType.GuildText,
|
|
1954
2099
|
parent: category.id,
|
|
1955
2100
|
topic: `${PROVIDER_LABELS[provider]} session | Dir: ${directory}`
|
|
1956
2101
|
});
|
|
1957
|
-
linkChannel(session.id, channel.id);
|
|
2102
|
+
await linkChannel(session.id, channel.id);
|
|
1958
2103
|
const fields = [
|
|
1959
2104
|
{ name: "Channel", value: `#${provider}-${session.id}`, inline: true },
|
|
1960
2105
|
{ name: "Provider", value: PROVIDER_LABELS[provider], inline: true },
|
|
@@ -1964,6 +2109,7 @@ async function handleSessionNew(interaction) {
|
|
|
1964
2109
|
if (session.tmuxName) {
|
|
1965
2110
|
fields.push({ name: "Terminal", value: `\`tmux attach -t ${session.tmuxName}\``, inline: false });
|
|
1966
2111
|
}
|
|
2112
|
+
addCodexPolicyFields(fields, codexOptions);
|
|
1967
2113
|
const embed = new EmbedBuilder3().setColor(3066993).setTitle(`Session Created: ${session.id}`).addFields(fields);
|
|
1968
2114
|
await interaction.editReply({ embeds: [embed] });
|
|
1969
2115
|
log(`Session "${session.id}" (${provider}) created by ${interaction.user.tag} in ${directory}`);
|
|
@@ -1974,6 +2120,7 @@ async function handleSessionNew(interaction) {
|
|
|
1974
2120
|
if (session.tmuxName) {
|
|
1975
2121
|
welcomeFields.push({ name: "Terminal Access", value: `\`tmux attach -t ${session.tmuxName}\``, inline: false });
|
|
1976
2122
|
}
|
|
2123
|
+
addCodexPolicyFields(welcomeFields, codexOptions);
|
|
1977
2124
|
welcomeEmbed.addFields(welcomeFields);
|
|
1978
2125
|
await channel.send({ embeds: [welcomeEmbed] });
|
|
1979
2126
|
} catch (err) {
|
|
@@ -1983,6 +2130,12 @@ async function handleSessionNew(interaction) {
|
|
|
1983
2130
|
} catch {
|
|
1984
2131
|
}
|
|
1985
2132
|
}
|
|
2133
|
+
if (session) {
|
|
2134
|
+
try {
|
|
2135
|
+
await endSession(session.id);
|
|
2136
|
+
} catch {
|
|
2137
|
+
}
|
|
2138
|
+
}
|
|
1986
2139
|
await interaction.editReply(`Failed to create session: ${err.message}`);
|
|
1987
2140
|
}
|
|
1988
2141
|
}
|
|
@@ -2094,6 +2247,7 @@ async function handleSessionResume(interaction) {
|
|
|
2094
2247
|
const providerSessionId = interaction.options.getString("session-id", true);
|
|
2095
2248
|
const name = interaction.options.getString("name", true);
|
|
2096
2249
|
const provider = interaction.options.getString("provider") || "claude";
|
|
2250
|
+
const codexOptions = resolveCodexSessionOptions(interaction, provider);
|
|
2097
2251
|
const directory = interaction.options.getString("directory") || config.defaultDirectory;
|
|
2098
2252
|
if (provider === "claude") {
|
|
2099
2253
|
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 +2261,27 @@ async function handleSessionResume(interaction) {
|
|
|
2107
2261
|
}
|
|
2108
2262
|
await interaction.deferReply();
|
|
2109
2263
|
let channel;
|
|
2264
|
+
let session;
|
|
2110
2265
|
try {
|
|
2111
2266
|
const guild = interaction.guild;
|
|
2112
2267
|
const projectName = projectNameFromDir(directory);
|
|
2113
2268
|
const { category } = await ensureProjectCategory(guild, projectName, directory);
|
|
2114
|
-
|
|
2269
|
+
session = await createSession(
|
|
2270
|
+
name,
|
|
2271
|
+
directory,
|
|
2272
|
+
"pending",
|
|
2273
|
+
projectName,
|
|
2274
|
+
provider,
|
|
2275
|
+
providerSessionId,
|
|
2276
|
+
codexOptions
|
|
2277
|
+
);
|
|
2115
2278
|
channel = await guild.channels.create({
|
|
2116
2279
|
name: `${provider}-${session.id}`,
|
|
2117
2280
|
type: ChannelType.GuildText,
|
|
2118
2281
|
parent: category.id,
|
|
2119
2282
|
topic: `${PROVIDER_LABELS[provider]} session (resumed) | Dir: ${directory}`
|
|
2120
2283
|
});
|
|
2121
|
-
linkChannel(session.id, channel.id);
|
|
2284
|
+
await linkChannel(session.id, channel.id);
|
|
2122
2285
|
const fields = [
|
|
2123
2286
|
{ name: "Channel", value: `#${provider}-${session.id}`, inline: true },
|
|
2124
2287
|
{ name: "Provider", value: PROVIDER_LABELS[provider], inline: true },
|
|
@@ -2129,6 +2292,7 @@ async function handleSessionResume(interaction) {
|
|
|
2129
2292
|
if (session.tmuxName) {
|
|
2130
2293
|
fields.push({ name: "Terminal", value: `\`tmux attach -t ${session.tmuxName}\``, inline: false });
|
|
2131
2294
|
}
|
|
2295
|
+
addCodexPolicyFields(fields, codexOptions);
|
|
2132
2296
|
const embed = new EmbedBuilder3().setColor(15105570).setTitle(`Session Resumed: ${session.id}`).addFields(fields);
|
|
2133
2297
|
await interaction.editReply({ embeds: [embed] });
|
|
2134
2298
|
log(`Session "${session.id}" (${provider}, resumed ${providerSessionId}) created by ${interaction.user.tag} in ${directory}`);
|
|
@@ -2139,6 +2303,7 @@ async function handleSessionResume(interaction) {
|
|
|
2139
2303
|
if (session.tmuxName) {
|
|
2140
2304
|
welcomeFields.push({ name: "Terminal Access", value: `\`tmux attach -t ${session.tmuxName}\``, inline: false });
|
|
2141
2305
|
}
|
|
2306
|
+
addCodexPolicyFields(welcomeFields, codexOptions);
|
|
2142
2307
|
await channel.send({
|
|
2143
2308
|
embeds: [
|
|
2144
2309
|
new EmbedBuilder3().setColor(15105570).setTitle(`${PROVIDER_LABELS[provider]} Session (Resumed)`).setDescription(
|
|
@@ -2153,6 +2318,12 @@ async function handleSessionResume(interaction) {
|
|
|
2153
2318
|
} catch {
|
|
2154
2319
|
}
|
|
2155
2320
|
}
|
|
2321
|
+
if (session) {
|
|
2322
|
+
try {
|
|
2323
|
+
await endSession(session.id);
|
|
2324
|
+
} catch {
|
|
2325
|
+
}
|
|
2326
|
+
}
|
|
2156
2327
|
await interaction.editReply(`Failed to resume session: ${err.message}`);
|
|
2157
2328
|
}
|
|
2158
2329
|
}
|
|
@@ -2174,7 +2345,13 @@ async function handleSessionList(interaction) {
|
|
|
2174
2345
|
const status = s.isGenerating ? "\u{1F7E2} generating" : "\u26AA idle";
|
|
2175
2346
|
const modeEmoji = { auto: "\u26A1", plan: "\u{1F4CB}", normal: "\u{1F6E1}\uFE0F" }[s.mode] || "\u26A1";
|
|
2176
2347
|
const providerTag = `[${s.provider}]`;
|
|
2177
|
-
|
|
2348
|
+
const codexPolicy = s.provider === "codex" ? [
|
|
2349
|
+
s.sandboxMode ? `sandbox:${s.sandboxMode}` : "",
|
|
2350
|
+
s.approvalPolicy ? `approval:${s.approvalPolicy}` : "",
|
|
2351
|
+
s.networkAccessEnabled !== void 0 ? `network:${s.networkAccessEnabled ? "on" : "off"}` : ""
|
|
2352
|
+
].filter(Boolean).join(" ") : "";
|
|
2353
|
+
const policySuffix = codexPolicy ? ` | ${codexPolicy}` : "";
|
|
2354
|
+
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
2355
|
});
|
|
2179
2356
|
embed.addFields({ name: `\u{1F4C1} ${project}`, value: lines.join("\n") });
|
|
2180
2357
|
}
|
|
@@ -3530,7 +3707,7 @@ async function startBot() {
|
|
|
3530
3707
|
client.on("messageCreate", handleMessage);
|
|
3531
3708
|
client.on("channelDelete", (channel) => {
|
|
3532
3709
|
if (channel.type === ChannelType2.GuildText) {
|
|
3533
|
-
unlinkChannel(channel.id);
|
|
3710
|
+
void unlinkChannel(channel.id);
|
|
3534
3711
|
}
|
|
3535
3712
|
});
|
|
3536
3713
|
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-C6RYOLAL.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);
|