acp-discord 0.7.1 → 0.8.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/README.md +32 -1
- package/dist/{chunk-IS5DAXVE.js → chunk-RKSH5HHZ.js} +48 -6
- package/dist/chunk-RKSH5HHZ.js.map +1 -0
- package/dist/daemon.js +571 -25
- package/dist/daemon.js.map +1 -1
- package/dist/index.js +1 -1
- package/dist/mcp-discord-channels.js +92 -2
- package/dist/mcp-discord-channels.js.map +1 -1
- package/dist/mcp-scheduled-tasks.d.ts +2 -0
- package/dist/mcp-scheduled-tasks.js +242 -0
- package/dist/mcp-scheduled-tasks.js.map +1 -0
- package/package.json +2 -1
- package/dist/chunk-IS5DAXVE.js.map +0 -1
package/README.md
CHANGED
|
@@ -12,6 +12,7 @@ Send a message in Discord, get AI coding assistance back — with tool call visu
|
|
|
12
12
|
- **Tool call visualization** — see what the agent is doing (⏳ pending → 🔄 running → ✅ done / ❌ failed), with a ⏹️ stop button to cancel
|
|
13
13
|
- **Permission UI** — Discord buttons for approving/denying agent actions, with file diffs shown inline for review before approval
|
|
14
14
|
- **Discord channel management** — agents can create/delete/modify Discord channels via MCP tools, with user confirmation for all mutating operations
|
|
15
|
+
- **Scheduled tasks** — agents can create once/cron/interval tasks that fire in isolated sessions, with configurable Discord notifications
|
|
15
16
|
- **Auto-reply mode** — optionally respond to all messages in a channel, not just mentions
|
|
16
17
|
- **Multi-agent support** — different channels can use different agents
|
|
17
18
|
- **Daemon mode** — runs in background with auto-start (systemd/launchd)
|
|
@@ -48,6 +49,7 @@ args = ["--acp"]
|
|
|
48
49
|
cwd = "/path/to/your/project"
|
|
49
50
|
idle_timeout = 600 # seconds before idle session is terminated (default: 600)
|
|
50
51
|
discord_tools = true # enable Discord channel management MCP tools (default: false)
|
|
52
|
+
scheduled_tasks = true # enable scheduled task MCP tools (default: false)
|
|
51
53
|
|
|
52
54
|
[channels.1234567890123456]
|
|
53
55
|
agent = "claude"
|
|
@@ -109,6 +111,30 @@ When `discord_tools = true` is set on an agent, the bot injects an MCP server th
|
|
|
109
111
|
|
|
110
112
|
All mutating operations (create, delete, update) require user approval via Discord buttons before executing. Newly created channels are automatically registered so the bot responds to messages there.
|
|
111
113
|
|
|
114
|
+
### Scheduled Tasks
|
|
115
|
+
|
|
116
|
+
When `scheduled_tasks = true` is set on an agent, the bot injects an MCP server that gives the agent tools to create and manage scheduled tasks. Each task fires by spawning an isolated, ephemeral agent session.
|
|
117
|
+
|
|
118
|
+
| Tool | Description | Requires Approval |
|
|
119
|
+
|------|-------------|:-----------------:|
|
|
120
|
+
| `list_scheduled_tasks` | List all scheduled tasks for this channel | No |
|
|
121
|
+
| `create_scheduled_task` | Create a new scheduled task | Yes |
|
|
122
|
+
| `update_scheduled_task` | Update an existing task | Yes |
|
|
123
|
+
| `delete_scheduled_task` | Delete a task | Yes |
|
|
124
|
+
| `get_task_logs` | Get execution history | No |
|
|
125
|
+
|
|
126
|
+
**Schedule types:**
|
|
127
|
+
- `once` — fire at a specific ISO datetime, then auto-complete
|
|
128
|
+
- `cron` — recurring via cron expression (e.g. `*/5 * * * *`)
|
|
129
|
+
- `interval` — recurring every N seconds, anchored to last run to prevent drift
|
|
130
|
+
|
|
131
|
+
**Notification modes** (per task, default `on_error`):
|
|
132
|
+
- `always` — post agent output to Discord after every run
|
|
133
|
+
- `on_error` — only post if execution fails
|
|
134
|
+
- `never` — silent, log only
|
|
135
|
+
|
|
136
|
+
Tasks are scoped to the channel that created them. Task data is persisted at `~/.acp-discord/scheduled-tasks.json` and execution history at `~/.acp-discord/task-run-logs.json`.
|
|
137
|
+
|
|
112
138
|
### Development
|
|
113
139
|
|
|
114
140
|
```bash
|
|
@@ -124,13 +150,18 @@ Discord User
|
|
|
124
150
|
↓ slash command / mention
|
|
125
151
|
Discord Bot (discord.js)
|
|
126
152
|
↓ channel routing ↑ IPC (Unix socket)
|
|
127
|
-
Session Manager MCP
|
|
153
|
+
Session Manager MCP Servers (discord-channels, scheduled-tasks)
|
|
128
154
|
↓ spawn agent subprocess ↑ MCP tools (stdio)
|
|
129
155
|
ACP Client (JSON-RPC over stdio)
|
|
130
156
|
↓ prompt / permissions / tool calls
|
|
131
157
|
Agent (claude-code, codex, etc.)
|
|
132
158
|
↑
|
|
133
159
|
Discord messages, embeds, buttons
|
|
160
|
+
|
|
161
|
+
TaskScheduler (15s poll loop)
|
|
162
|
+
↓ fires due tasks
|
|
163
|
+
Ephemeral ACP Session (isolated, destroyed after)
|
|
164
|
+
↓ conditionally posts to Discord (based on notify setting)
|
|
134
165
|
```
|
|
135
166
|
|
|
136
167
|
## License
|
|
@@ -28,8 +28,8 @@ function isDaemonRunning(pidPath) {
|
|
|
28
28
|
}
|
|
29
29
|
|
|
30
30
|
// src/shared/config.ts
|
|
31
|
-
import { parse } from "smol-toml";
|
|
32
|
-
import { readFileSync as readFileSync2 } from "fs";
|
|
31
|
+
import { parse, stringify } from "smol-toml";
|
|
32
|
+
import { readFileSync as readFileSync2, writeFileSync as writeFileSync2 } from "fs";
|
|
33
33
|
function parseConfig(toml) {
|
|
34
34
|
const raw = parse(toml);
|
|
35
35
|
const discord = raw.discord;
|
|
@@ -64,12 +64,16 @@ function parseConfig(toml) {
|
|
|
64
64
|
if (agent.discord_tools !== void 0 && typeof agent.discord_tools !== "boolean") {
|
|
65
65
|
throw new Error(`agents.${name}.discord_tools must be a boolean`);
|
|
66
66
|
}
|
|
67
|
+
if (agent.scheduled_tasks !== void 0 && typeof agent.scheduled_tasks !== "boolean") {
|
|
68
|
+
throw new Error(`agents.${name}.scheduled_tasks must be a boolean`);
|
|
69
|
+
}
|
|
67
70
|
parsedAgents[name] = {
|
|
68
71
|
command: agent.command,
|
|
69
72
|
args: agent.args ?? [],
|
|
70
73
|
cwd: typeof agent.cwd === "string" ? agent.cwd : process.cwd(),
|
|
71
74
|
idle_timeout: typeof agent.idle_timeout === "number" ? agent.idle_timeout : 600,
|
|
72
|
-
discord_tools: agent.discord_tools === true
|
|
75
|
+
discord_tools: agent.discord_tools === true,
|
|
76
|
+
scheduled_tasks: agent.scheduled_tasks === true
|
|
73
77
|
};
|
|
74
78
|
}
|
|
75
79
|
const channels = raw.channels ?? {};
|
|
@@ -91,11 +95,15 @@ function parseConfig(toml) {
|
|
|
91
95
|
if (ch.discord_tools !== void 0 && typeof ch.discord_tools !== "boolean") {
|
|
92
96
|
throw new Error(`channels.${id}.discord_tools must be a boolean`);
|
|
93
97
|
}
|
|
98
|
+
if (ch.scheduled_tasks !== void 0 && typeof ch.scheduled_tasks !== "boolean") {
|
|
99
|
+
throw new Error(`channels.${id}.scheduled_tasks must be a boolean`);
|
|
100
|
+
}
|
|
94
101
|
parsedChannels[id] = {
|
|
95
102
|
agent: agentRef,
|
|
96
103
|
cwd: ch.cwd ? String(ch.cwd) : void 0,
|
|
97
104
|
auto_reply: ch.auto_reply === true,
|
|
98
|
-
discord_tools: ch.discord_tools ?? void 0
|
|
105
|
+
discord_tools: ch.discord_tools ?? void 0,
|
|
106
|
+
scheduled_tasks: ch.scheduled_tasks ?? void 0
|
|
99
107
|
};
|
|
100
108
|
}
|
|
101
109
|
return {
|
|
@@ -108,6 +116,38 @@ function loadConfig(configPath) {
|
|
|
108
116
|
const content = readFileSync2(configPath, "utf-8");
|
|
109
117
|
return parseConfig(content);
|
|
110
118
|
}
|
|
119
|
+
function saveConfig(configPath, config) {
|
|
120
|
+
const tomlObj = {
|
|
121
|
+
discord: { token: config.discord.token },
|
|
122
|
+
agents: {},
|
|
123
|
+
channels: {}
|
|
124
|
+
};
|
|
125
|
+
const agents = tomlObj.agents;
|
|
126
|
+
for (const [name, agent] of Object.entries(config.agents)) {
|
|
127
|
+
const a = {
|
|
128
|
+
command: agent.command
|
|
129
|
+
};
|
|
130
|
+
if (agent.args.length > 0) a.args = agent.args;
|
|
131
|
+
if (agent.cwd !== process.cwd()) a.cwd = agent.cwd;
|
|
132
|
+
if (agent.idle_timeout !== 600) a.idle_timeout = agent.idle_timeout;
|
|
133
|
+
if (agent.discord_tools) a.discord_tools = agent.discord_tools;
|
|
134
|
+
if ("scheduled_tasks" in agent && agent.scheduled_tasks) a.scheduled_tasks = agent.scheduled_tasks;
|
|
135
|
+
agents[name] = a;
|
|
136
|
+
}
|
|
137
|
+
const channels = tomlObj.channels;
|
|
138
|
+
for (const [id, ch] of Object.entries(config.channels)) {
|
|
139
|
+
const c = {
|
|
140
|
+
agent: ch.agent
|
|
141
|
+
};
|
|
142
|
+
if (ch.cwd !== void 0) c.cwd = ch.cwd;
|
|
143
|
+
if (ch.auto_reply !== void 0) c.auto_reply = ch.auto_reply;
|
|
144
|
+
if (ch.discord_tools !== void 0) c.discord_tools = ch.discord_tools;
|
|
145
|
+
if ("scheduled_tasks" in ch && ch.scheduled_tasks !== void 0) c.scheduled_tasks = ch.scheduled_tasks;
|
|
146
|
+
channels[id] = c;
|
|
147
|
+
}
|
|
148
|
+
const toml = stringify(tomlObj);
|
|
149
|
+
writeFileSync2(configPath, toml, "utf-8");
|
|
150
|
+
}
|
|
111
151
|
function resolveChannelConfig(config, channelId) {
|
|
112
152
|
const channelConf = config.channels[channelId];
|
|
113
153
|
if (!channelConf) return null;
|
|
@@ -119,7 +159,8 @@ function resolveChannelConfig(config, channelId) {
|
|
|
119
159
|
agent: {
|
|
120
160
|
...agentConf,
|
|
121
161
|
cwd: channelConf.cwd ?? agentConf.cwd,
|
|
122
|
-
discord_tools: channelConf.discord_tools ?? agentConf.discord_tools
|
|
162
|
+
discord_tools: channelConf.discord_tools ?? agentConf.discord_tools,
|
|
163
|
+
scheduled_tasks: channelConf.scheduled_tasks ?? agentConf.scheduled_tasks
|
|
123
164
|
},
|
|
124
165
|
autoReply: channelConf.auto_reply === true
|
|
125
166
|
};
|
|
@@ -132,6 +173,7 @@ export {
|
|
|
132
173
|
isDaemonRunning,
|
|
133
174
|
parseConfig,
|
|
134
175
|
loadConfig,
|
|
176
|
+
saveConfig,
|
|
135
177
|
resolveChannelConfig
|
|
136
178
|
};
|
|
137
|
-
//# sourceMappingURL=chunk-
|
|
179
|
+
//# sourceMappingURL=chunk-RKSH5HHZ.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/cli/pid.ts","../src/shared/config.ts"],"sourcesContent":["import { readFileSync, writeFileSync, unlinkSync, existsSync, mkdirSync } from \"node:fs\";\nimport { dirname } from \"node:path\";\n\nexport function writePid(pidPath: string, pid: number): void {\n mkdirSync(dirname(pidPath), { recursive: true });\n writeFileSync(pidPath, String(pid), \"utf-8\");\n}\n\nexport function readPid(pidPath: string): number | null {\n if (!existsSync(pidPath)) return null;\n const content = readFileSync(pidPath, \"utf-8\").trim();\n const pid = parseInt(content, 10);\n return isNaN(pid) ? null : pid;\n}\n\nexport function removePid(pidPath: string): void {\n if (existsSync(pidPath)) unlinkSync(pidPath);\n}\n\nexport function isDaemonRunning(pidPath: string): boolean {\n const pid = readPid(pidPath);\n if (pid === null) return false;\n try {\n process.kill(pid, 0); // signal 0 = check if process exists\n return true;\n } catch {\n return false;\n }\n}\n","import { parse, stringify } from \"smol-toml\";\nimport { readFileSync, writeFileSync } from \"node:fs\";\nimport type { AppConfig, ChannelConfig, ResolvedChannelConfig } from \"./types.js\";\n\nexport function parseConfig(toml: string): AppConfig {\n const raw = parse(toml) as Record<string, unknown>;\n\n const discord = raw.discord as Record<string, unknown> | undefined;\n if (!discord?.token || typeof discord.token !== \"string\") {\n throw new Error(\"Missing required: discord.token\");\n }\n if (discord.token.trim().length === 0) {\n throw new Error(\"discord.token must not be empty\");\n }\n\n const agents = raw.agents as Record<string, Record<string, unknown>> | undefined;\n if (!agents || Object.keys(agents).length === 0) {\n throw new Error(\"Missing required: at least one agent in [agents.*]\");\n }\n\n const parsedAgents: AppConfig[\"agents\"] = {};\n for (const [name, agent] of Object.entries(agents)) {\n // Validate command is a non-empty string\n if (!agent.command || typeof agent.command !== \"string\") {\n throw new Error(`agents.${name}.command must be a non-empty string`);\n }\n\n // Validate args is an array of strings\n if (agent.args !== undefined) {\n if (!Array.isArray(agent.args) || !agent.args.every((a: unknown) => typeof a === \"string\")) {\n throw new Error(`agents.${name}.args must be an array of strings`);\n }\n }\n\n // Validate idle_timeout is a positive number\n if (agent.idle_timeout !== undefined) {\n if (typeof agent.idle_timeout !== \"number\" || agent.idle_timeout <= 0) {\n throw new Error(`agents.${name}.idle_timeout must be a positive number`);\n }\n }\n\n // Validate cwd is a string if provided\n if (agent.cwd !== undefined && typeof agent.cwd !== \"string\") {\n throw new Error(`agents.${name}.cwd must be a string`);\n }\n\n // Validate discord_tools is a boolean if provided\n if (agent.discord_tools !== undefined && typeof agent.discord_tools !== \"boolean\") {\n throw new Error(`agents.${name}.discord_tools must be a boolean`);\n }\n\n // Validate scheduled_tasks is a boolean if provided\n if (agent.scheduled_tasks !== undefined && typeof agent.scheduled_tasks !== \"boolean\") {\n throw new Error(`agents.${name}.scheduled_tasks must be a boolean`);\n }\n\n parsedAgents[name] = {\n command: agent.command,\n args: (agent.args as string[]) ?? [],\n cwd: typeof agent.cwd === \"string\" ? agent.cwd : process.cwd(),\n idle_timeout: typeof agent.idle_timeout === \"number\" ? agent.idle_timeout : 600,\n discord_tools: agent.discord_tools === true,\n scheduled_tasks: agent.scheduled_tasks === true,\n };\n }\n\n const channels = (raw.channels ?? {}) as Record<string, Record<string, unknown>>;\n const parsedChannels: AppConfig[\"channels\"] = {};\n for (const [id, ch] of Object.entries(channels)) {\n const agentRef = ch.agent ?? \"default\";\n if (typeof agentRef !== \"string\") {\n throw new Error(`channels.${id}.agent must be a string`);\n }\n if (!parsedAgents[agentRef]) {\n throw new Error(`channels.${id}.agent references unknown agent \"${agentRef}\"`);\n }\n if (ch.cwd !== undefined && typeof ch.cwd !== \"string\") {\n throw new Error(`channels.${id}.cwd must be a string`);\n }\n if (ch.auto_reply !== undefined && typeof ch.auto_reply !== \"boolean\") {\n throw new Error(`channels.${id}.auto_reply must be a boolean`);\n }\n if (ch.discord_tools !== undefined && typeof ch.discord_tools !== \"boolean\") {\n throw new Error(`channels.${id}.discord_tools must be a boolean`);\n }\n if (ch.scheduled_tasks !== undefined && typeof ch.scheduled_tasks !== \"boolean\") {\n throw new Error(`channels.${id}.scheduled_tasks must be a boolean`);\n }\n\n parsedChannels[id] = {\n agent: agentRef,\n cwd: ch.cwd ? String(ch.cwd) : undefined,\n auto_reply: ch.auto_reply === true,\n discord_tools: ch.discord_tools ?? undefined,\n scheduled_tasks: ch.scheduled_tasks ?? undefined,\n };\n }\n\n return {\n discord: { token: String(discord.token) },\n agents: parsedAgents,\n channels: parsedChannels,\n };\n}\n\nexport function loadConfig(configPath: string): AppConfig {\n const content = readFileSync(configPath, \"utf-8\");\n return parseConfig(content);\n}\n\nexport function saveConfig(configPath: string, config: AppConfig): void {\n // Convert AppConfig to a plain object suitable for smol-toml stringify\n const tomlObj: Record<string, unknown> = {\n discord: { token: config.discord.token },\n agents: {} as Record<string, Record<string, unknown>>,\n channels: {} as Record<string, Record<string, unknown>>,\n };\n\n const agents = tomlObj.agents as Record<string, Record<string, unknown>>;\n for (const [name, agent] of Object.entries(config.agents)) {\n const a: Record<string, unknown> = {\n command: agent.command,\n };\n if (agent.args.length > 0) a.args = agent.args;\n if (agent.cwd !== process.cwd()) a.cwd = agent.cwd;\n if (agent.idle_timeout !== 600) a.idle_timeout = agent.idle_timeout;\n if (agent.discord_tools) a.discord_tools = agent.discord_tools;\n if (\"scheduled_tasks\" in agent && agent.scheduled_tasks) a.scheduled_tasks = agent.scheduled_tasks;\n agents[name] = a;\n }\n\n const channels = tomlObj.channels as Record<string, Record<string, unknown>>;\n for (const [id, ch] of Object.entries(config.channels)) {\n const c: Record<string, unknown> = {\n agent: ch.agent,\n };\n if (ch.cwd !== undefined) c.cwd = ch.cwd;\n if (ch.auto_reply !== undefined) c.auto_reply = ch.auto_reply;\n if (ch.discord_tools !== undefined) c.discord_tools = ch.discord_tools;\n if (\"scheduled_tasks\" in ch && ch.scheduled_tasks !== undefined) c.scheduled_tasks = ch.scheduled_tasks;\n channels[id] = c;\n }\n\n const toml = stringify(tomlObj);\n writeFileSync(configPath, toml, \"utf-8\");\n}\n\nexport function addChannelToConfig(\n configPath: string,\n channelId: string,\n channelConfig: ChannelConfig,\n): AppConfig {\n const config = loadConfig(configPath);\n config.channels[channelId] = channelConfig;\n saveConfig(configPath, config);\n return config;\n}\n\nexport function removeChannelFromConfig(\n configPath: string,\n channelId: string,\n): AppConfig {\n const config = loadConfig(configPath);\n delete config.channels[channelId];\n saveConfig(configPath, config);\n return config;\n}\n\nexport function resolveChannelConfig(\n config: AppConfig,\n channelId: string,\n): ResolvedChannelConfig | null {\n const channelConf = config.channels[channelId];\n if (!channelConf) return null;\n\n const agentConf = config.agents[channelConf.agent];\n if (!agentConf) return null;\n\n return {\n channelId,\n agentName: channelConf.agent,\n agent: {\n ...agentConf,\n cwd: channelConf.cwd ?? agentConf.cwd,\n discord_tools: channelConf.discord_tools ?? agentConf.discord_tools,\n scheduled_tasks: channelConf.scheduled_tasks ?? agentConf.scheduled_tasks,\n },\n autoReply: channelConf.auto_reply === true,\n };\n}\n"],"mappings":"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","names":["readFileSync","writeFileSync"]}
|