agentcord 0.1.9 → 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) {
|
|
@@ -738,6 +805,12 @@ function detectNumberedOptions(text) {
|
|
|
738
805
|
const hasQuestion = /\?\s*$/.test(preamble.trim()) || /\b(which|choose|select|pick|prefer|would you like|how would you|what approach|option)\b/.test(preamble);
|
|
739
806
|
return hasQuestion ? options : null;
|
|
740
807
|
}
|
|
808
|
+
var ABORT_PATTERNS = ["abort", "cancel", "interrupt", "killed", "signal"];
|
|
809
|
+
function isAbortError(err) {
|
|
810
|
+
if (err instanceof Error && err.name === "AbortError") return true;
|
|
811
|
+
const msg = (err.message || "").toLowerCase();
|
|
812
|
+
return ABORT_PATTERNS.some((p) => msg.includes(p));
|
|
813
|
+
}
|
|
741
814
|
function detectYesNoPrompt(text) {
|
|
742
815
|
const lower = text.toLowerCase();
|
|
743
816
|
return /\b(y\/n|yes\/no|confirm|proceed)\b/.test(lower) || /\?\s*$/.test(text.trim()) && /\b(should|would you|do you want|shall)\b/.test(lower);
|
|
@@ -753,6 +826,7 @@ var MODE_PROMPTS = {
|
|
|
753
826
|
var sessionStore = new Store("sessions.json");
|
|
754
827
|
var sessions = /* @__PURE__ */ new Map();
|
|
755
828
|
var channelToSession = /* @__PURE__ */ new Map();
|
|
829
|
+
var saveQueue = Promise.resolve();
|
|
756
830
|
function tmux(...args) {
|
|
757
831
|
return new Promise((resolve2, reject) => {
|
|
758
832
|
execFile("tmux", args, { encoding: "utf-8" }, (err, stdout) => {
|
|
@@ -769,10 +843,24 @@ async function tmuxSessionExists(tmuxName) {
|
|
|
769
843
|
return false;
|
|
770
844
|
}
|
|
771
845
|
}
|
|
846
|
+
function isPlaceholderChannelId(channelId) {
|
|
847
|
+
return !channelId || channelId === "pending";
|
|
848
|
+
}
|
|
772
849
|
async function loadSessions() {
|
|
773
850
|
const data = await sessionStore.read();
|
|
774
851
|
if (!data) return;
|
|
852
|
+
let cleaned = false;
|
|
775
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
|
+
}
|
|
776
864
|
const provider = s.provider ?? "claude";
|
|
777
865
|
const providerSessionId = s.providerSessionId ?? s.claudeSessionId;
|
|
778
866
|
if (provider === "claude") {
|
|
@@ -795,11 +883,15 @@ async function loadSessions() {
|
|
|
795
883
|
});
|
|
796
884
|
channelToSession.set(s.channelId, s.id);
|
|
797
885
|
}
|
|
886
|
+
if (cleaned) {
|
|
887
|
+
await saveSessions();
|
|
888
|
+
}
|
|
798
889
|
console.log(`Restored ${sessions.size} session(s)`);
|
|
799
890
|
}
|
|
800
|
-
async function
|
|
891
|
+
async function persistSessionsNow() {
|
|
801
892
|
const data = [];
|
|
802
893
|
for (const [, s] of sessions) {
|
|
894
|
+
if (isPlaceholderChannelId(s.channelId)) continue;
|
|
803
895
|
data.push({
|
|
804
896
|
id: s.id,
|
|
805
897
|
channelId: s.channelId,
|
|
@@ -809,6 +901,9 @@ async function saveSessions() {
|
|
|
809
901
|
tmuxName: s.tmuxName,
|
|
810
902
|
providerSessionId: s.providerSessionId,
|
|
811
903
|
model: s.model,
|
|
904
|
+
sandboxMode: s.sandboxMode,
|
|
905
|
+
approvalPolicy: s.approvalPolicy,
|
|
906
|
+
networkAccessEnabled: s.networkAccessEnabled,
|
|
812
907
|
agentPersona: s.agentPersona,
|
|
813
908
|
verbose: s.verbose || void 0,
|
|
814
909
|
mode: s.mode !== "auto" ? s.mode : void 0,
|
|
@@ -820,8 +915,24 @@ async function saveSessions() {
|
|
|
820
915
|
}
|
|
821
916
|
await sessionStore.write(data);
|
|
822
917
|
}
|
|
823
|
-
|
|
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 = {}) {
|
|
824
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;
|
|
825
936
|
if (!isPathAllowed(resolvedDir, config.allowedPaths)) {
|
|
826
937
|
throw new Error(`Directory not in allowed paths: ${resolvedDir}`);
|
|
827
938
|
}
|
|
@@ -849,6 +960,9 @@ async function createSession(name, directory, channelId, projectName, provider =
|
|
|
849
960
|
provider,
|
|
850
961
|
tmuxName,
|
|
851
962
|
providerSessionId,
|
|
963
|
+
sandboxMode: effectiveOptions.sandboxMode,
|
|
964
|
+
approvalPolicy: effectiveOptions.approvalPolicy,
|
|
965
|
+
networkAccessEnabled: effectiveOptions.networkAccessEnabled,
|
|
852
966
|
verbose: false,
|
|
853
967
|
mode: "auto",
|
|
854
968
|
isGenerating: false,
|
|
@@ -858,8 +972,10 @@ async function createSession(name, directory, channelId, projectName, provider =
|
|
|
858
972
|
totalCost: 0
|
|
859
973
|
};
|
|
860
974
|
sessions.set(id, session);
|
|
861
|
-
|
|
862
|
-
|
|
975
|
+
if (!isPlaceholderChannelId(channelId)) {
|
|
976
|
+
channelToSession.set(channelId, id);
|
|
977
|
+
await saveSessions();
|
|
978
|
+
}
|
|
863
979
|
return session;
|
|
864
980
|
}
|
|
865
981
|
function getSession(id) {
|
|
@@ -884,29 +1000,38 @@ async function endSession(id) {
|
|
|
884
1000
|
} catch {
|
|
885
1001
|
}
|
|
886
1002
|
}
|
|
887
|
-
|
|
1003
|
+
if (!isPlaceholderChannelId(session.channelId)) {
|
|
1004
|
+
channelToSession.delete(session.channelId);
|
|
1005
|
+
}
|
|
888
1006
|
sessions.delete(id);
|
|
889
1007
|
await saveSessions();
|
|
890
1008
|
}
|
|
891
|
-
function linkChannel(sessionId, channelId) {
|
|
1009
|
+
async function linkChannel(sessionId, channelId) {
|
|
892
1010
|
const session = sessions.get(sessionId);
|
|
893
|
-
if (session) {
|
|
1011
|
+
if (!session) {
|
|
1012
|
+
throw new Error(`Session "${sessionId}" not found`);
|
|
1013
|
+
}
|
|
1014
|
+
if (!isPlaceholderChannelId(session.channelId)) {
|
|
894
1015
|
channelToSession.delete(session.channelId);
|
|
895
|
-
session.channelId = channelId;
|
|
896
|
-
channelToSession.set(channelId, sessionId);
|
|
897
|
-
saveSessions();
|
|
898
1016
|
}
|
|
1017
|
+
session.channelId = channelId;
|
|
1018
|
+
channelToSession.set(channelId, sessionId);
|
|
1019
|
+
await saveSessions();
|
|
899
1020
|
}
|
|
900
|
-
function unlinkChannel(channelId) {
|
|
901
|
-
|
|
902
|
-
if (sessionId) {
|
|
903
|
-
|
|
904
|
-
|
|
905
|
-
|
|
906
|
-
|
|
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
|
+
}
|
|
907
1029
|
}
|
|
908
|
-
saveSessions();
|
|
909
1030
|
}
|
|
1031
|
+
if (!sessionId) return;
|
|
1032
|
+
channelToSession.delete(channelId);
|
|
1033
|
+
sessions.delete(sessionId);
|
|
1034
|
+
await saveSessions();
|
|
910
1035
|
}
|
|
911
1036
|
function setModel(sessionId, model) {
|
|
912
1037
|
const session = sessions.get(sessionId);
|
|
@@ -948,13 +1073,6 @@ function buildSystemPromptParts(session) {
|
|
|
948
1073
|
if (modePrompt) parts.push(modePrompt);
|
|
949
1074
|
return parts;
|
|
950
1075
|
}
|
|
951
|
-
function resetProviderSession(sessionId) {
|
|
952
|
-
const session = sessions.get(sessionId);
|
|
953
|
-
if (session) {
|
|
954
|
-
session.providerSessionId = void 0;
|
|
955
|
-
saveSessions();
|
|
956
|
-
}
|
|
957
|
-
}
|
|
958
1076
|
async function* sendPrompt(sessionId, prompt) {
|
|
959
1077
|
const session = sessions.get(sessionId);
|
|
960
1078
|
if (!session) throw new Error(`Session "${sessionId}" not found`);
|
|
@@ -970,6 +1088,9 @@ async function* sendPrompt(sessionId, prompt) {
|
|
|
970
1088
|
directory: session.directory,
|
|
971
1089
|
providerSessionId: session.providerSessionId,
|
|
972
1090
|
model: session.model,
|
|
1091
|
+
sandboxMode: session.sandboxMode,
|
|
1092
|
+
approvalPolicy: session.approvalPolicy,
|
|
1093
|
+
networkAccessEnabled: session.networkAccessEnabled,
|
|
973
1094
|
systemPromptParts,
|
|
974
1095
|
abortController: controller
|
|
975
1096
|
});
|
|
@@ -985,7 +1106,7 @@ async function* sendPrompt(sessionId, prompt) {
|
|
|
985
1106
|
}
|
|
986
1107
|
session.messageCount++;
|
|
987
1108
|
} catch (err) {
|
|
988
|
-
if (err
|
|
1109
|
+
if (isAbortError(err)) {
|
|
989
1110
|
} else {
|
|
990
1111
|
throw err;
|
|
991
1112
|
}
|
|
@@ -1011,6 +1132,9 @@ async function* continueSession(sessionId) {
|
|
|
1011
1132
|
directory: session.directory,
|
|
1012
1133
|
providerSessionId: session.providerSessionId,
|
|
1013
1134
|
model: session.model,
|
|
1135
|
+
sandboxMode: session.sandboxMode,
|
|
1136
|
+
approvalPolicy: session.approvalPolicy,
|
|
1137
|
+
networkAccessEnabled: session.networkAccessEnabled,
|
|
1014
1138
|
systemPromptParts,
|
|
1015
1139
|
abortController: controller
|
|
1016
1140
|
});
|
|
@@ -1026,7 +1150,7 @@ async function* continueSession(sessionId) {
|
|
|
1026
1150
|
}
|
|
1027
1151
|
session.messageCount++;
|
|
1028
1152
|
} catch (err) {
|
|
1029
|
-
if (err
|
|
1153
|
+
if (isAbortError(err)) {
|
|
1030
1154
|
} else {
|
|
1031
1155
|
throw err;
|
|
1032
1156
|
}
|
|
@@ -1254,15 +1378,6 @@ function renderCodexTodoListEmbed(event) {
|
|
|
1254
1378
|
}
|
|
1255
1379
|
|
|
1256
1380
|
// src/output-handler.ts
|
|
1257
|
-
var ABORT_PATTERNS = ["abort", "cancel", "interrupt", "killed", "signal"];
|
|
1258
|
-
function isAbortLike(err) {
|
|
1259
|
-
if (err.name === "AbortError") return true;
|
|
1260
|
-
const msg = (err.message || "").toLowerCase();
|
|
1261
|
-
return ABORT_PATTERNS.some((p) => msg.includes(p));
|
|
1262
|
-
}
|
|
1263
|
-
function isAbortError(errors) {
|
|
1264
|
-
return errors.some((e) => ABORT_PATTERNS.some((p) => e.toLowerCase().includes(p)));
|
|
1265
|
-
}
|
|
1266
1381
|
var expandableStore = /* @__PURE__ */ new Map();
|
|
1267
1382
|
var expandCounter = 0;
|
|
1268
1383
|
var pendingAnswersStore = /* @__PURE__ */ new Map();
|
|
@@ -1730,10 +1845,6 @@ ${statusLine}`);
|
|
|
1730
1845
|
${event.errors.join("\n")}
|
|
1731
1846
|
\`\`\``);
|
|
1732
1847
|
}
|
|
1733
|
-
if (!event.success && !isAbortError(event.errors)) {
|
|
1734
|
-
resetProviderSession(sessionId);
|
|
1735
|
-
streamer.append("\n-# Session reset \u2014 next message will start a fresh provider session.");
|
|
1736
|
-
}
|
|
1737
1848
|
await streamer.finalize();
|
|
1738
1849
|
const components = [];
|
|
1739
1850
|
const checkText = lastText || "";
|
|
@@ -1762,13 +1873,11 @@ ${event.message}
|
|
|
1762
1873
|
}
|
|
1763
1874
|
} catch (err) {
|
|
1764
1875
|
await streamer.finalize();
|
|
1765
|
-
|
|
1766
|
-
|
|
1767
|
-
resetProviderSession(sessionId);
|
|
1876
|
+
if (!isAbortError(err)) {
|
|
1877
|
+
const errMsg = err.message || "";
|
|
1768
1878
|
const embed = new EmbedBuilder2().setColor(15158332).setTitle("Error").setDescription(`\`\`\`
|
|
1769
1879
|
${errMsg}
|
|
1770
|
-
|
|
1771
|
-
-# Session reset \u2014 next message will start a fresh provider session.`);
|
|
1880
|
+
\`\`\``);
|
|
1772
1881
|
await channel.send({ embeds: [embed] });
|
|
1773
1882
|
}
|
|
1774
1883
|
} finally {
|
|
@@ -1945,9 +2054,28 @@ var PROVIDER_COLORS = {
|
|
|
1945
2054
|
claude: 3447003,
|
|
1946
2055
|
codex: 1090431
|
|
1947
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
|
+
}
|
|
1948
2075
|
async function handleSessionNew(interaction) {
|
|
1949
2076
|
const name = interaction.options.getString("name", true);
|
|
1950
2077
|
const provider = interaction.options.getString("provider") || "claude";
|
|
2078
|
+
const codexOptions = resolveCodexSessionOptions(interaction, provider);
|
|
1951
2079
|
let directory = interaction.options.getString("directory");
|
|
1952
2080
|
if (!directory) {
|
|
1953
2081
|
const parentId = interaction.channel?.parentId;
|
|
@@ -1959,18 +2087,19 @@ async function handleSessionNew(interaction) {
|
|
|
1959
2087
|
}
|
|
1960
2088
|
await interaction.deferReply();
|
|
1961
2089
|
let channel;
|
|
2090
|
+
let session;
|
|
1962
2091
|
try {
|
|
1963
2092
|
const guild = interaction.guild;
|
|
1964
2093
|
const projectName = projectNameFromDir(directory);
|
|
1965
2094
|
const { category } = await ensureProjectCategory(guild, projectName, directory);
|
|
1966
|
-
|
|
2095
|
+
session = await createSession(name, directory, "pending", projectName, provider, void 0, codexOptions);
|
|
1967
2096
|
channel = await guild.channels.create({
|
|
1968
2097
|
name: `${provider}-${session.id}`,
|
|
1969
2098
|
type: ChannelType.GuildText,
|
|
1970
2099
|
parent: category.id,
|
|
1971
2100
|
topic: `${PROVIDER_LABELS[provider]} session | Dir: ${directory}`
|
|
1972
2101
|
});
|
|
1973
|
-
linkChannel(session.id, channel.id);
|
|
2102
|
+
await linkChannel(session.id, channel.id);
|
|
1974
2103
|
const fields = [
|
|
1975
2104
|
{ name: "Channel", value: `#${provider}-${session.id}`, inline: true },
|
|
1976
2105
|
{ name: "Provider", value: PROVIDER_LABELS[provider], inline: true },
|
|
@@ -1980,6 +2109,7 @@ async function handleSessionNew(interaction) {
|
|
|
1980
2109
|
if (session.tmuxName) {
|
|
1981
2110
|
fields.push({ name: "Terminal", value: `\`tmux attach -t ${session.tmuxName}\``, inline: false });
|
|
1982
2111
|
}
|
|
2112
|
+
addCodexPolicyFields(fields, codexOptions);
|
|
1983
2113
|
const embed = new EmbedBuilder3().setColor(3066993).setTitle(`Session Created: ${session.id}`).addFields(fields);
|
|
1984
2114
|
await interaction.editReply({ embeds: [embed] });
|
|
1985
2115
|
log(`Session "${session.id}" (${provider}) created by ${interaction.user.tag} in ${directory}`);
|
|
@@ -1990,6 +2120,7 @@ async function handleSessionNew(interaction) {
|
|
|
1990
2120
|
if (session.tmuxName) {
|
|
1991
2121
|
welcomeFields.push({ name: "Terminal Access", value: `\`tmux attach -t ${session.tmuxName}\``, inline: false });
|
|
1992
2122
|
}
|
|
2123
|
+
addCodexPolicyFields(welcomeFields, codexOptions);
|
|
1993
2124
|
welcomeEmbed.addFields(welcomeFields);
|
|
1994
2125
|
await channel.send({ embeds: [welcomeEmbed] });
|
|
1995
2126
|
} catch (err) {
|
|
@@ -1999,6 +2130,12 @@ async function handleSessionNew(interaction) {
|
|
|
1999
2130
|
} catch {
|
|
2000
2131
|
}
|
|
2001
2132
|
}
|
|
2133
|
+
if (session) {
|
|
2134
|
+
try {
|
|
2135
|
+
await endSession(session.id);
|
|
2136
|
+
} catch {
|
|
2137
|
+
}
|
|
2138
|
+
}
|
|
2002
2139
|
await interaction.editReply(`Failed to create session: ${err.message}`);
|
|
2003
2140
|
}
|
|
2004
2141
|
}
|
|
@@ -2110,6 +2247,7 @@ async function handleSessionResume(interaction) {
|
|
|
2110
2247
|
const providerSessionId = interaction.options.getString("session-id", true);
|
|
2111
2248
|
const name = interaction.options.getString("name", true);
|
|
2112
2249
|
const provider = interaction.options.getString("provider") || "claude";
|
|
2250
|
+
const codexOptions = resolveCodexSessionOptions(interaction, provider);
|
|
2113
2251
|
const directory = interaction.options.getString("directory") || config.defaultDirectory;
|
|
2114
2252
|
if (provider === "claude") {
|
|
2115
2253
|
const uuidRegex = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
|
|
@@ -2123,18 +2261,27 @@ async function handleSessionResume(interaction) {
|
|
|
2123
2261
|
}
|
|
2124
2262
|
await interaction.deferReply();
|
|
2125
2263
|
let channel;
|
|
2264
|
+
let session;
|
|
2126
2265
|
try {
|
|
2127
2266
|
const guild = interaction.guild;
|
|
2128
2267
|
const projectName = projectNameFromDir(directory);
|
|
2129
2268
|
const { category } = await ensureProjectCategory(guild, projectName, directory);
|
|
2130
|
-
|
|
2269
|
+
session = await createSession(
|
|
2270
|
+
name,
|
|
2271
|
+
directory,
|
|
2272
|
+
"pending",
|
|
2273
|
+
projectName,
|
|
2274
|
+
provider,
|
|
2275
|
+
providerSessionId,
|
|
2276
|
+
codexOptions
|
|
2277
|
+
);
|
|
2131
2278
|
channel = await guild.channels.create({
|
|
2132
2279
|
name: `${provider}-${session.id}`,
|
|
2133
2280
|
type: ChannelType.GuildText,
|
|
2134
2281
|
parent: category.id,
|
|
2135
2282
|
topic: `${PROVIDER_LABELS[provider]} session (resumed) | Dir: ${directory}`
|
|
2136
2283
|
});
|
|
2137
|
-
linkChannel(session.id, channel.id);
|
|
2284
|
+
await linkChannel(session.id, channel.id);
|
|
2138
2285
|
const fields = [
|
|
2139
2286
|
{ name: "Channel", value: `#${provider}-${session.id}`, inline: true },
|
|
2140
2287
|
{ name: "Provider", value: PROVIDER_LABELS[provider], inline: true },
|
|
@@ -2145,6 +2292,7 @@ async function handleSessionResume(interaction) {
|
|
|
2145
2292
|
if (session.tmuxName) {
|
|
2146
2293
|
fields.push({ name: "Terminal", value: `\`tmux attach -t ${session.tmuxName}\``, inline: false });
|
|
2147
2294
|
}
|
|
2295
|
+
addCodexPolicyFields(fields, codexOptions);
|
|
2148
2296
|
const embed = new EmbedBuilder3().setColor(15105570).setTitle(`Session Resumed: ${session.id}`).addFields(fields);
|
|
2149
2297
|
await interaction.editReply({ embeds: [embed] });
|
|
2150
2298
|
log(`Session "${session.id}" (${provider}, resumed ${providerSessionId}) created by ${interaction.user.tag} in ${directory}`);
|
|
@@ -2155,6 +2303,7 @@ async function handleSessionResume(interaction) {
|
|
|
2155
2303
|
if (session.tmuxName) {
|
|
2156
2304
|
welcomeFields.push({ name: "Terminal Access", value: `\`tmux attach -t ${session.tmuxName}\``, inline: false });
|
|
2157
2305
|
}
|
|
2306
|
+
addCodexPolicyFields(welcomeFields, codexOptions);
|
|
2158
2307
|
await channel.send({
|
|
2159
2308
|
embeds: [
|
|
2160
2309
|
new EmbedBuilder3().setColor(15105570).setTitle(`${PROVIDER_LABELS[provider]} Session (Resumed)`).setDescription(
|
|
@@ -2169,6 +2318,12 @@ async function handleSessionResume(interaction) {
|
|
|
2169
2318
|
} catch {
|
|
2170
2319
|
}
|
|
2171
2320
|
}
|
|
2321
|
+
if (session) {
|
|
2322
|
+
try {
|
|
2323
|
+
await endSession(session.id);
|
|
2324
|
+
} catch {
|
|
2325
|
+
}
|
|
2326
|
+
}
|
|
2172
2327
|
await interaction.editReply(`Failed to resume session: ${err.message}`);
|
|
2173
2328
|
}
|
|
2174
2329
|
}
|
|
@@ -2190,7 +2345,13 @@ async function handleSessionList(interaction) {
|
|
|
2190
2345
|
const status = s.isGenerating ? "\u{1F7E2} generating" : "\u26AA idle";
|
|
2191
2346
|
const modeEmoji = { auto: "\u26A1", plan: "\u{1F4CB}", normal: "\u{1F6E1}\uFE0F" }[s.mode] || "\u26A1";
|
|
2192
2347
|
const providerTag = `[${s.provider}]`;
|
|
2193
|
-
|
|
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}`;
|
|
2194
2355
|
});
|
|
2195
2356
|
embed.addFields({ name: `\u{1F4C1} ${project}`, value: lines.join("\n") });
|
|
2196
2357
|
}
|
|
@@ -3055,17 +3216,7 @@ async function handleMessage(message) {
|
|
|
3055
3216
|
userLastMessage.set(message.author.id, now);
|
|
3056
3217
|
if (session.isGenerating) {
|
|
3057
3218
|
abortSession(session.id);
|
|
3058
|
-
|
|
3059
|
-
while (session.isGenerating && Date.now() < deadline) {
|
|
3060
|
-
await new Promise((r) => setTimeout(r, 100));
|
|
3061
|
-
}
|
|
3062
|
-
if (session.isGenerating) {
|
|
3063
|
-
await message.reply({
|
|
3064
|
-
content: "Could not interrupt the current generation. Try `/session stop`.",
|
|
3065
|
-
allowedMentions: { repliedUser: false }
|
|
3066
|
-
});
|
|
3067
|
-
return;
|
|
3068
|
-
}
|
|
3219
|
+
await new Promise((r) => setTimeout(r, 200));
|
|
3069
3220
|
}
|
|
3070
3221
|
const text = message.content.trim();
|
|
3071
3222
|
const imageAttachments = message.attachments.filter(
|
|
@@ -3126,15 +3277,11 @@ ${result.value.content}
|
|
|
3126
3277
|
const stream = sendPrompt(session.id, prompt);
|
|
3127
3278
|
await handleOutputStream(stream, channel, session.id, session.verbose, session.mode, session.provider);
|
|
3128
3279
|
} catch (err) {
|
|
3129
|
-
|
|
3130
|
-
const isAbort = err.name === "AbortError" || /abort|cancel|interrupt/i.test(errMsg);
|
|
3131
|
-
if (isAbort) {
|
|
3280
|
+
if (isAbortError(err)) {
|
|
3132
3281
|
return;
|
|
3133
3282
|
}
|
|
3134
|
-
resetProviderSession(session.id);
|
|
3135
3283
|
await message.reply({
|
|
3136
|
-
content: `Error: ${
|
|
3137
|
-
-# Session reset \u2014 next message will start a fresh provider session.`,
|
|
3284
|
+
content: `Error: ${err.message || "Unknown error"}`,
|
|
3138
3285
|
allowedMentions: { repliedUser: false }
|
|
3139
3286
|
});
|
|
3140
3287
|
}
|
|
@@ -3560,7 +3707,7 @@ async function startBot() {
|
|
|
3560
3707
|
client.on("messageCreate", handleMessage);
|
|
3561
3708
|
client.on("channelDelete", (channel) => {
|
|
3562
3709
|
if (channel.type === ChannelType2.GuildText) {
|
|
3563
|
-
unlinkChannel(channel.id);
|
|
3710
|
+
void unlinkChannel(channel.id);
|
|
3564
3711
|
}
|
|
3565
3712
|
});
|
|
3566
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);
|