discord-os 0.3.0__tar.gz
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.
- discord_os-0.3.0/LICENSE +21 -0
- discord_os-0.3.0/PKG-INFO +291 -0
- discord_os-0.3.0/README.md +264 -0
- discord_os-0.3.0/pyproject.toml +45 -0
- discord_os-0.3.0/setup.cfg +4 -0
- discord_os-0.3.0/src/agent_discord/__init__.py +12 -0
- discord_os-0.3.0/src/agent_discord/__main__.py +3 -0
- discord_os-0.3.0/src/agent_discord/bootstrap.py +79 -0
- discord_os-0.3.0/src/agent_discord/cli.py +1393 -0
- discord_os-0.3.0/src/agent_discord/config.py +331 -0
- discord_os-0.3.0/src/agent_discord/contracts.py +456 -0
- discord_os-0.3.0/src/agent_discord/discord/__init__.py +14 -0
- discord_os-0.3.0/src/agent_discord/discord/chunking.py +57 -0
- discord_os-0.3.0/src/agent_discord/discord/errors.py +27 -0
- discord_os-0.3.0/src/agent_discord/discord/facade.py +256 -0
- discord_os-0.3.0/src/agent_discord/discord/gateway.py +65 -0
- discord_os-0.3.0/src/agent_discord/discord/interactions.py +245 -0
- discord_os-0.3.0/src/agent_discord/discord/invite.py +25 -0
- discord_os-0.3.0/src/agent_discord/discord/object_store.py +255 -0
- discord_os-0.3.0/src/agent_discord/discord/providers/__init__.py +74 -0
- discord_os-0.3.0/src/agent_discord/discord/providers/base.py +394 -0
- discord_os-0.3.0/src/agent_discord/discord/providers/braindao.py +163 -0
- discord_os-0.3.0/src/agent_discord/discord/providers/fake.py +388 -0
- discord_os-0.3.0/src/agent_discord/discord/providers/rest.py +154 -0
- discord_os-0.3.0/src/agent_discord/discord/providers/saseq.py +616 -0
- discord_os-0.3.0/src/agent_discord/discord/realtime.py +157 -0
- discord_os-0.3.0/src/agent_discord/discord/rest.py +428 -0
- discord_os-0.3.0/src/agent_discord/discord/ws.py +167 -0
- discord_os-0.3.0/src/agent_discord/host/__init__.py +18 -0
- discord_os-0.3.0/src/agent_discord/host/actions.py +126 -0
- discord_os-0.3.0/src/agent_discord/host/install.py +212 -0
- discord_os-0.3.0/src/agent_discord/host/panel.py +128 -0
- discord_os-0.3.0/src/agent_discord/host/power.py +35 -0
- discord_os-0.3.0/src/agent_discord/host/service.py +146 -0
- discord_os-0.3.0/src/agent_discord/host/verbs.py +110 -0
- discord_os-0.3.0/src/agent_discord/keys/__init__.py +10 -0
- discord_os-0.3.0/src/agent_discord/keys/connect.py +278 -0
- discord_os-0.3.0/src/agent_discord/keys/vault.py +171 -0
- discord_os-0.3.0/src/agent_discord/marionette/__init__.py +17 -0
- discord_os-0.3.0/src/agent_discord/marionette/backend.py +438 -0
- discord_os-0.3.0/src/agent_discord/marionette/fake.py +150 -0
- discord_os-0.3.0/src/agent_discord/marionette/transport.py +77 -0
- discord_os-0.3.0/src/agent_discord/orchestration/__init__.py +14 -0
- discord_os-0.3.0/src/agent_discord/orchestration/cards.py +120 -0
- discord_os-0.3.0/src/agent_discord/orchestration/listen.py +440 -0
- discord_os-0.3.0/src/agent_discord/orchestration/orchestrator.py +497 -0
- discord_os-0.3.0/src/agent_discord/orchestration/receipts.py +72 -0
- discord_os-0.3.0/src/agent_discord/persistence/__init__.py +6 -0
- discord_os-0.3.0/src/agent_discord/persistence/research.py +389 -0
- discord_os-0.3.0/src/agent_discord/persistence/sqlite.py +859 -0
- discord_os-0.3.0/src/agent_discord/puppetmaster/__init__.py +23 -0
- discord_os-0.3.0/src/agent_discord/puppetmaster/agentic.py +207 -0
- discord_os-0.3.0/src/agent_discord/puppetmaster/backend.py +333 -0
- discord_os-0.3.0/src/agent_discord/puppetmaster/fake.py +144 -0
- discord_os-0.3.0/src/agent_discord/puppetmaster/models.py +22 -0
- discord_os-0.3.0/src/agent_discord/redaction.py +54 -0
- discord_os-0.3.0/src/discord_os.egg-info/PKG-INFO +291 -0
- discord_os-0.3.0/src/discord_os.egg-info/SOURCES.txt +78 -0
- discord_os-0.3.0/src/discord_os.egg-info/dependency_links.txt +1 -0
- discord_os-0.3.0/src/discord_os.egg-info/entry_points.txt +3 -0
- discord_os-0.3.0/src/discord_os.egg-info/requires.txt +6 -0
- discord_os-0.3.0/src/discord_os.egg-info/top_level.txt +1 -0
- discord_os-0.3.0/tests/test_bootstrap_config.py +154 -0
- discord_os-0.3.0/tests/test_chunking_and_facade.py +151 -0
- discord_os-0.3.0/tests/test_cli.py +66 -0
- discord_os-0.3.0/tests/test_connect_os.py +662 -0
- discord_os-0.3.0/tests/test_discord_os.py +620 -0
- discord_os-0.3.0/tests/test_discord_rest.py +235 -0
- discord_os-0.3.0/tests/test_host_actions.py +175 -0
- discord_os-0.3.0/tests/test_host_panel.py +204 -0
- discord_os-0.3.0/tests/test_host_power.py +210 -0
- discord_os-0.3.0/tests/test_interactions.py +205 -0
- discord_os-0.3.0/tests/test_invite.py +34 -0
- discord_os-0.3.0/tests/test_marionette_backend.py +118 -0
- discord_os-0.3.0/tests/test_mcp_transport.py +207 -0
- discord_os-0.3.0/tests/test_model_pin.py +31 -0
- discord_os-0.3.0/tests/test_orchestration.py +151 -0
- discord_os-0.3.0/tests/test_puppetmaster_cli_backend.py +147 -0
- discord_os-0.3.0/tests/test_research_memory.py +235 -0
- discord_os-0.3.0/tests/test_sqlite_memory.py +145 -0
discord_os-0.3.0/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Discord OS contributors
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the Software), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED AS IS, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
@@ -0,0 +1,291 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: discord-os
|
|
3
|
+
Version: 0.3.0
|
|
4
|
+
Summary: Discord OS: Discord is the harness UI for local agent work. Artifacts are Discord objects (snowflake IDs).
|
|
5
|
+
Author: Discord OS contributors
|
|
6
|
+
License: MIT
|
|
7
|
+
Project-URL: Homepage, https://github.com/professorpalmer/agent-discord
|
|
8
|
+
Project-URL: Attribution-SaseQ, https://github.com/SaseQ/discord-mcp
|
|
9
|
+
Project-URL: Attribution-BrainDAO, https://github.com/BrainDAO/mcp-discord
|
|
10
|
+
Keywords: discord,discord-os,mcp,puppetmaster,agent,cli
|
|
11
|
+
Classifier: Development Status :: 3 - Alpha
|
|
12
|
+
Classifier: Environment :: Console
|
|
13
|
+
Classifier: Intended Audience :: Developers
|
|
14
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
15
|
+
Classifier: Programming Language :: Python :: 3
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
18
|
+
Classifier: Topic :: Communications :: Chat
|
|
19
|
+
Requires-Python: >=3.11
|
|
20
|
+
Description-Content-Type: text/markdown
|
|
21
|
+
License-File: LICENSE
|
|
22
|
+
Provides-Extra: dev
|
|
23
|
+
Requires-Dist: pytest>=8.0; extra == "dev"
|
|
24
|
+
Provides-Extra: interactions
|
|
25
|
+
Requires-Dist: PyNaCl>=1.5; extra == "interactions"
|
|
26
|
+
Dynamic: license-file
|
|
27
|
+
|
|
28
|
+
# Discord OS
|
|
29
|
+
|
|
30
|
+
Discord is the screen. This process is the computer. Your phone is the remote.
|
|
31
|
+
|
|
32
|
+
This is **your** bot on **your** machine. There is no hosted fleet to invite. Leave the host running; turn work on and off from Discord.
|
|
33
|
+
|
|
34
|
+
The GitHub repo is [`professorpalmer/agent-discord`](https://github.com/professorpalmer/agent-discord). The `agent-discord` command still works. Env vars and the `.agent-discord` workspace directory stay as they are.
|
|
35
|
+
|
|
36
|
+
## 1. Make a Discord bot
|
|
37
|
+
|
|
38
|
+
Do this once in a browser.
|
|
39
|
+
|
|
40
|
+
1. Open the [Discord Developer Portal](https://discord.com/developers/applications) and sign in.
|
|
41
|
+
2. **New Application**. Name it whatever you want (this is the bot people will see).
|
|
42
|
+
3. Left sidebar → **Bot**.
|
|
43
|
+
4. **Reset Token** / **Copy**. The token looks like `xxx.yyy.zzz`. That is `DISCORD_BOT_TOKEN`.
|
|
44
|
+
- Do **not** use Application ID.
|
|
45
|
+
- Do **not** use the OAuth client secret.
|
|
46
|
+
5. On the same Bot page, enable **Message Content Intent**. Save changes.
|
|
47
|
+
6. Left sidebar → **General Information**. Copy **Application ID** (digits only). That is `DISCORD_APPLICATION_ID`.
|
|
48
|
+
7. In Discord, create or pick a private staff channel. Copy its channel ID (Developer Mode → right-click the channel → Copy Channel ID).
|
|
49
|
+
|
|
50
|
+
## 2. Install once on the machine that will do the work
|
|
51
|
+
|
|
52
|
+
Python 3.11+. From a clone of `dev` today (`pip install discord-os` after PyPI):
|
|
53
|
+
|
|
54
|
+
```bash
|
|
55
|
+
python3 -m venv .venv
|
|
56
|
+
source .venv/bin/activate # Windows: .venv\Scripts\activate
|
|
57
|
+
pip install -e .
|
|
58
|
+
pip install puppetmaster-ai # compute kernel; not bundled
|
|
59
|
+
export OPENROUTER_API_KEY=... # never commit
|
|
60
|
+
discord-os bootstrap
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
Put these in `.env` (or write the bot token to `~/.pmharness/.discord_token`, mode 0600):
|
|
64
|
+
|
|
65
|
+
```bash
|
|
66
|
+
DISCORD_BOT_TOKEN=xxx.yyy.zzz
|
|
67
|
+
DISCORD_APPLICATION_ID=123456789012345678
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
Then one command:
|
|
71
|
+
|
|
72
|
+
```bash
|
|
73
|
+
discord-os setup --channel-id YOUR_CHANNEL_ID
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
That invites the bot (open the printed URL), installs a login helper so it comes back after reboot, and posts a HOST card in the channel with **On** and **Off** buttons. You do not type commands in Discord after this.
|
|
77
|
+
|
|
78
|
+
## 3. Use it from Discord
|
|
79
|
+
|
|
80
|
+
| In Discord | What happens |
|
|
81
|
+
|---|---|
|
|
82
|
+
| **On** | Starts work on the host. Type a normal sentence as a task. |
|
|
83
|
+
| **Off** | Stops work. The helper stays so On still works from your phone. |
|
|
84
|
+
| a normal sentence | A task, only while On. |
|
|
85
|
+
|
|
86
|
+
If Discord logs the bot out, work stops. The login helper starts again idle.
|
|
87
|
+
|
|
88
|
+
No Docker. No slash commands. No public URL. No `/on` to remember.
|
|
89
|
+
|
|
90
|
+
Default I/O is Discord REST. SaseQ/BrainDAO MCP is optional if you already run those servers.
|
|
91
|
+
|
|
92
|
+
## Thesis
|
|
93
|
+
|
|
94
|
+
Discord already is the phone UI, the ACL (a channel you control), the notification bus, and the identity layer. Discord OS treats **artifacts as Discord objects**:
|
|
95
|
+
|
|
96
|
+
```text
|
|
97
|
+
object bytes
|
|
98
|
+
→ attachment + caption on a channel message
|
|
99
|
+
→ Discord CDN (ephemeral signed URL, ~24h)
|
|
100
|
+
→ durable key = channel_id / message_id / attachment_id (+ sha256)
|
|
101
|
+
→ retrieve later by re-fetching the message (fresh URL), then downloading
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
Never persist a CDN URL as the durable key. Official retrieve is Get Channel Message (or `POST /attachments/refresh-urls`). Jump links (`https://discord.com/channels/{guild|@me}/{channel}/{message}`) are what we print for humans.
|
|
105
|
+
|
|
106
|
+
## Prior art (steal economics, not product)
|
|
107
|
+
|
|
108
|
+
Two piles exist. We are neither.
|
|
109
|
+
|
|
110
|
+
| Pile | Examples | What they do | What we take | What we refuse |
|
|
111
|
+
|------|----------|--------------|--------------|----------------|
|
|
112
|
+
| Discord-as-disk | DiscordFS, forscht/ddrive, KITdt/discord-drive, missuo/discord-image | Chunk files into channels as free S3 / WebDav | Snowflake ID pointer; refresh-on-get | Unlimited chunked S3, public CDN, WebDav, 4TB multipart |
|
|
113
|
+
| Discord-as-agent-relay | Discode, Agent4Discord, Agentboard, cursor-mobile-bridge, claudecode-discord | Channel=workspace, thread=run, phone UI; artifacts stay on local disk/tmux | Channel as workspace / ACL; phone-native receipts | Relaying progress while leaving blobs on disk only |
|
|
114
|
+
|
|
115
|
+
Discord has said: if you host files on Discord, find a more suitable service. Honest S3 replacement here means **agent artifacts under Discord size limits in your own staff channel**. It does not mean a public CDN.
|
|
116
|
+
|
|
117
|
+
Default live I/O is **Discord REST** with the Bot token (`DISCORD_BOT_TOKEN` or `~/.pmharness/.discord_token`). That value is the Bot token (`xxx.yyy.zzz`) from the Bot tab — not the Application client ID and not the OAuth client secret. Optional SaseQ/BrainDAO MCP adapters try file-tool names first; if the catalog has no file tool they fall back to the same REST path. We still refuse to base64-dump files into `send_message`. The fake provider is the hermetic proof of the protocol. Foreground `listen` does **not** open a Discord Gateway. `host` / `setup` opens a Gateway **only** so On/Off buttons work — no public URL. Do not run that beside another bot process that already owns the Gateway.
|
|
118
|
+
|
|
119
|
+
## Honest limits
|
|
120
|
+
|
|
121
|
+
- Default object cap is **10 MiB** (`DISCORD_MAX_OBJECT_BYTES=10485760`). Discord free is roughly 10–25MB; Nitro is higher. Configurable, not unlimited.
|
|
122
|
+
- Discord ToS: conversation artifacts in **your** server, not a public CDN or anonymous disk.
|
|
123
|
+
- Not a compute host. Default compute is `AGENT_DISCORD_COMPUTE=auto`: Puppetmaster **agentic** (`openrouter/auto`) when an OpenRouter key is on the host or in the workspace vault; otherwise the Cursor pin (`cursor/grok-4-5` / adapter `grok-4.5`). **No silent model fallback.**
|
|
124
|
+
- No DiscordFS-style multipart chunking. Oversize artifacts become an `overflow` pointer + local stash, not multipart CDN objects.
|
|
125
|
+
- `/connect <secret>` and `/open` are message-prefix verbs by default, not Discord slash Interactions. Discord still sees shred payloads once before delete.
|
|
126
|
+
- `listen` ignores channel history older than a durable per-channel SQLite watermark (first listen: now minus 15s, same slack as process start; later processes resume from the stored high-water, including skipped cards/connects/opens) so a seeded staff channel is not dispatched as an implement job.
|
|
127
|
+
|
|
128
|
+
## What a run does
|
|
129
|
+
|
|
130
|
+
1. **Connect** (optional): `/connect` on the listen host inherits `OPENROUTER_API_KEY`, shreds a pasted secret after delete, or mints a pairing ticket for `discord-os connect --ticket`.
|
|
131
|
+
2. **Intake** a natural-language task from `run` or the host loop (staff channel / phone). Cards, receipts, and object-store captions are skipped. On/Off buttons, `/connect`, and `/open` are intercepted before task dispatch. Work is accepted only while On. Discord is the remote; this process opens Terminal, the file manager, or an allowlisted browser on the host.
|
|
132
|
+
3. **Snapshot** scoped context from SQLite memory + channel bindings.
|
|
133
|
+
4. **Dispatch** to Puppetmaster agentic (OpenRouter/BYOK) or the Cursor pin, depending on resolved compute.
|
|
134
|
+
5. **Persist** events, then **put** backend file artifacts through the object store (overflow pointer + stash when over the cap). Local path is kept if put fails.
|
|
135
|
+
6. **Relay** Discord-safe `**Card**` progress (edited in place when possible) and a receipt that shows kind + jump URL (never a CDN URL, never hidden chain-of-thought).
|
|
136
|
+
|
|
137
|
+
## Optional MCP Discord servers
|
|
138
|
+
|
|
139
|
+
Default I/O is REST. These adapters are optional. **Upstream source code is not copied.**
|
|
140
|
+
|
|
141
|
+
| Provider | Repository | License | Notes |
|
|
142
|
+
|----------|------------|---------|-------|
|
|
143
|
+
| **SaseQ / discord-mcp** | https://github.com/SaseQ/discord-mcp | MIT | HTTP endpoint convention via `SASEQ_MCP_HTTP_URL` (default `http://127.0.0.1:8085/mcp`). Prefer HTTP; stdio requires an explicit `DISCORD_MCP_STDIO_COMMAND` (no fabricated npm default). |
|
|
144
|
+
| **BrainDAO / mcp-discord** (`@iqai`) | https://github.com/BrainDAO/mcp-discord | MIT | Sampling/tool convention exposed as an adapter seam; **one Gateway owner per bot token** — no second Gateway required. Stdio requires an explicit `DISCORD_MCP_STDIO_COMMAND` such as `npx -y @iqai/mcp-discord`. |
|
|
145
|
+
|
|
146
|
+
See [`THIRD_PARTY_NOTICES.md`](THIRD_PARTY_NOTICES.md) for details.
|
|
147
|
+
|
|
148
|
+
## Dev / fake path
|
|
149
|
+
|
|
150
|
+
```bash
|
|
151
|
+
# Requires Python 3.11+
|
|
152
|
+
python -m venv .venv
|
|
153
|
+
source .venv/bin/activate
|
|
154
|
+
pip install -e ".[dev]"
|
|
155
|
+
|
|
156
|
+
cp .env.example .env
|
|
157
|
+
# DISCORD_BOT_TOKEN = Bot token (xxx.yyy.zzz), never Application ID or OAuth secret
|
|
158
|
+
|
|
159
|
+
discord-os bootstrap
|
|
160
|
+
discord-os check
|
|
161
|
+
|
|
162
|
+
# Dry-run (fake Discord + fake Puppetmaster — no network)
|
|
163
|
+
discord-os run "Summarize open items" --channel-id 123 --fake --no-discord-post
|
|
164
|
+
discord-os put ./notes.bin --channel-id 123 --fake --json
|
|
165
|
+
discord-os get MESSAGE_ID --channel-id 123 --out ./got.bin --fake --json
|
|
166
|
+
discord-os listen --channel-id 123 --fake --once
|
|
167
|
+
discord-os connect --provider openrouter --from-env --json
|
|
168
|
+
discord-os status --json
|
|
169
|
+
```
|
|
170
|
+
|
|
171
|
+
### Discord providers
|
|
172
|
+
|
|
173
|
+
- Default: `DISCORD_MCP_PROVIDER=rest` (official API, no MCP process)
|
|
174
|
+
- Optional: `saseq` or `braindao` plus `DISCORD_MCP_TRANSPORT=http` or `stdio`
|
|
175
|
+
- HTTP URLs: `SASEQ_MCP_HTTP_URL` (default `http://127.0.0.1:8085/mcp`) / `BRAINDAO_MCP_HTTP_URL`
|
|
176
|
+
- Stdio: `DISCORD_MCP_STDIO_COMMAND` is **required** when `transport=stdio`
|
|
177
|
+
- `DISCORD_MAX_OBJECT_BYTES` (optional; default `10485760`)
|
|
178
|
+
|
|
179
|
+
MCP catalog discovery is runtime-only. Missing file tools fall back to REST. CDN URLs stay ephemeral.
|
|
180
|
+
|
|
181
|
+
### Compute and keys
|
|
182
|
+
|
|
183
|
+
| Path | How the key arrives | What Discord sees |
|
|
184
|
+
|------|---------------------|-------------------|
|
|
185
|
+
| Host inherit | `OPENROUTER_API_KEY` already on the listen host; `/connect` or `connect --from-env` | Fingerprint only |
|
|
186
|
+
| Pairing ticket | `/connect` with no secret mints an 8-char ticket (15 min); paste the key on host stdin | Ticket code only |
|
|
187
|
+
| Shred absorb | `/connect <secret>` or `!connect <secret>` | Payload once, then delete; card is fingerprint only |
|
|
188
|
+
|
|
189
|
+
Vault files live under `{workspace}/keys/` (ciphertext + `master.key`). Never commit them. Agentic dispatch injects the key into the **subprocess env** as `OPENROUTER_API_KEY` — never argv, never logs.
|
|
190
|
+
|
|
191
|
+
### Host surfaces (poverty default)
|
|
192
|
+
|
|
193
|
+
Discord is the remote. The listen host opens local surfaces the same way a desktop harness does:
|
|
194
|
+
|
|
195
|
+
| Verb | What opens |
|
|
196
|
+
|------|------------|
|
|
197
|
+
| `/open` or `!open` (default: files) | File manager at the workspace-relative path |
|
|
198
|
+
| `/open terminal [path]` | Terminal at that path |
|
|
199
|
+
| `/open files [path]` | File manager |
|
|
200
|
+
| `/open browser <url>` or `/open https://…` | Browser, allowlisted only |
|
|
201
|
+
|
|
202
|
+
Paths stay inside `PUPPETMASTER_CWD` and the workspace. `~` is rejected. Browser URLs are loopback `http(s)` to `127.0.0.1` or `localhost`, or Discord channel jump links (`https://discord.com/channels/…`). Same engine: `discord-os open terminal|files|browser`.
|
|
203
|
+
|
|
204
|
+
Slash chrome is **opt-in** and does not replace `listen`. Set `AGENT_DISCORD_INTERACTIONS=http`, install `pip install 'discord-os[interactions]'` for Ed25519 verify, then `discord-os interactions --register --guild-id ID` and `--serve`. Bind is loopback (`127.0.0.1:8743`). If you want Discord to POST Interactions, you tunnel that URL and paste **your** public HTTPS URL into Developer Portal → Interactions Endpoint URL. Do not paste a tunnel URL into chat. Slash `/connect` has **no secret option** — inherit, ticket, or host CLI only. This does not open a second Gateway.
|
|
205
|
+
|
|
206
|
+
### Puppetmaster model pin
|
|
207
|
+
|
|
208
|
+
| Field | Value |
|
|
209
|
+
|-------|-------|
|
|
210
|
+
| Compute default | `AGENT_DISCORD_COMPUTE=auto` |
|
|
211
|
+
| Agentic canonical / adapter | `openrouter/auto` |
|
|
212
|
+
| Canonical Cursor model (receipts/audit) | `cursor/grok-4-5` |
|
|
213
|
+
| Cursor adapter (`puppetmaster cursor --model`) | `grok-4.5` |
|
|
214
|
+
| Cursor allowlist | **only** `cursor/grok-4-5` |
|
|
215
|
+
| Agentic allowlist | **only** `openrouter/auto` |
|
|
216
|
+
|
|
217
|
+
Requests for any other model raise an error. There is **no** silent remap. Cursor compute still invokes `puppetmaster cursor …`. Agentic compute invokes `puppetmaster agentic … --provider openrouter --mode implement`. Set `PUPPETMASTER_CWD` to control `--cwd`.
|
|
218
|
+
|
|
219
|
+
### Optional Marionette backend
|
|
220
|
+
|
|
221
|
+
Default `AGENT_DISCORD_BACKEND=puppetmaster`. To opt in:
|
|
222
|
+
|
|
223
|
+
```bash
|
|
224
|
+
AGENT_DISCORD_BACKEND=marionette
|
|
225
|
+
MARIONETTE_BASE_URL=http://127.0.0.1:8787 # your local Marionette HTTP API
|
|
226
|
+
# Optional path overrides (defaults shown):
|
|
227
|
+
# MARIONETTE_SESSIONS_PATH=/v1/sessions
|
|
228
|
+
# MARIONETTE_JOBS_PATH=/v1/jobs
|
|
229
|
+
```
|
|
230
|
+
|
|
231
|
+
The adapter documents an expected session/job/events/status/cancel contract; it does **not** pretend an unverified endpoint is guaranteed. Missing `MARIONETTE_BASE_URL` or transport failures surface as configuration/transport errors.
|
|
232
|
+
|
|
233
|
+
## CLI
|
|
234
|
+
|
|
235
|
+
```text
|
|
236
|
+
discord-os bootstrap [--workspace PATH]
|
|
237
|
+
discord-os check [--allow-empty-token] [--live] [--channel-id ID]
|
|
238
|
+
discord-os run TASK --channel-id ID [--message-id ID] [--fake] [--no-discord-post] [--json]
|
|
239
|
+
discord-os setup --channel-id ID
|
|
240
|
+
discord-os host start --channel-id ID
|
|
241
|
+
discord-os host stop
|
|
242
|
+
discord-os host status
|
|
243
|
+
discord-os listen --channel-id ID [--once] [--interval SEC] [--fake] [--json]
|
|
244
|
+
discord-os connect [--provider openrouter] [--ticket T] [--from-env] [--json]
|
|
245
|
+
discord-os status [--json]
|
|
246
|
+
discord-os invite [--application-id ID] [--json]
|
|
247
|
+
discord-os open {terminal,files,browser} [PATH_OR_URL] [--json]
|
|
248
|
+
discord-os interactions [--register] [--guild-id ID] [--serve] [--json]
|
|
249
|
+
discord-os put PATH --channel-id ID [--thread-id ID] [--guild-id ID] [--kind blob] [--fake] [--json]
|
|
250
|
+
discord-os get MESSAGE_ID --channel-id ID [--attachment-id ID] [--out PATH] [--fake] [--json]
|
|
251
|
+
discord-os ls --channel-id ID [--run-id ID] [--fake] [--json]
|
|
252
|
+
```
|
|
253
|
+
|
|
254
|
+
`put` / `get` / `ls --fake` need no network. `get` writes bytes to `--out`, or to stdout only when stdout is not a tty (otherwise `--out` is required). Pointer JSON never includes a `url` key.
|
|
255
|
+
|
|
256
|
+
Also: `python -m agent_discord …`
|
|
257
|
+
|
|
258
|
+
## Architecture (small & readable)
|
|
259
|
+
|
|
260
|
+
```text
|
|
261
|
+
CLI → Orchestrator → backend (Puppetmaster agentic | Puppetmaster cursor | optional Marionette HTTP | fake)
|
|
262
|
+
↘ SQLite (bindings, tasks, runs, events, memory, artifacts + object pointers,
|
|
263
|
+
inbound message dedupe, gateway ownership,
|
|
264
|
+
optional research claims / leases / negatives)
|
|
265
|
+
↘ Discord facade → object store → REST (default) | optional SaseQ/BrainDAO | fake
|
|
266
|
+
```
|
|
267
|
+
|
|
268
|
+
Message intake is REST. The host process opens a Discord Gateway only for On/Off buttons. The SQLite gateway row is a local one-process lock and is stolen if the previous owner pid is dead.
|
|
269
|
+
|
|
270
|
+
- **stdlib-first** core; optional `pytest` for development.
|
|
271
|
+
- Explicit typed contracts + dependency injection — tests never need Discord, Cursor, or network.
|
|
272
|
+
- Durable object key is `DiscordObjectRef` (channel / message / attachment / sha256). Channel id is the ACL; `get` refuses a mismatched caller channel (confused-deputy).
|
|
273
|
+
- Gateway exclusivity is **durable** in SQLite across concurrent local processes (in-memory registry retained for unit tests).
|
|
274
|
+
- Inbound Discord message IDs are deduplicated at orchestration level (prior receipt reused or explicit ignored-duplicate result).
|
|
275
|
+
- Event/artifact payloads recursively strip forbidden hidden-reasoning keys.
|
|
276
|
+
- BrainDAO sampling-compatible ingress is an adapter seam on the same facade.
|
|
277
|
+
- **Research memory** (optional orchestration context): typed claims with deterministic fingerprints, atomic leases, provenance/evidence, and queryable negative findings. Ordinary memory recall is unchanged; research metadata is not required for normal tasks.
|
|
278
|
+
- **Marionette backend** (optional, explicit opt-in via `AGENT_DISCORD_BACKEND=marionette`): stdlib urllib adapter with configurable endpoint paths and injectable transport for tests. Default remains Puppetmaster. Unconfigured/unavailable Marionette fails closed — no silent fallback. Model field still carries the canonical pin `cursor/grok-4-5` → adapter `grok-4.5`.
|
|
279
|
+
|
|
280
|
+
## Development / tests
|
|
281
|
+
|
|
282
|
+
```bash
|
|
283
|
+
pip install -e ".[dev]"
|
|
284
|
+
pytest
|
|
285
|
+
```
|
|
286
|
+
|
|
287
|
+
Tests use fake MCP, Puppetmaster, and Marionette providers only.
|
|
288
|
+
|
|
289
|
+
## License
|
|
290
|
+
|
|
291
|
+
MIT — see [`LICENSE`](LICENSE).
|
|
@@ -0,0 +1,264 @@
|
|
|
1
|
+
# Discord OS
|
|
2
|
+
|
|
3
|
+
Discord is the screen. This process is the computer. Your phone is the remote.
|
|
4
|
+
|
|
5
|
+
This is **your** bot on **your** machine. There is no hosted fleet to invite. Leave the host running; turn work on and off from Discord.
|
|
6
|
+
|
|
7
|
+
The GitHub repo is [`professorpalmer/agent-discord`](https://github.com/professorpalmer/agent-discord). The `agent-discord` command still works. Env vars and the `.agent-discord` workspace directory stay as they are.
|
|
8
|
+
|
|
9
|
+
## 1. Make a Discord bot
|
|
10
|
+
|
|
11
|
+
Do this once in a browser.
|
|
12
|
+
|
|
13
|
+
1. Open the [Discord Developer Portal](https://discord.com/developers/applications) and sign in.
|
|
14
|
+
2. **New Application**. Name it whatever you want (this is the bot people will see).
|
|
15
|
+
3. Left sidebar → **Bot**.
|
|
16
|
+
4. **Reset Token** / **Copy**. The token looks like `xxx.yyy.zzz`. That is `DISCORD_BOT_TOKEN`.
|
|
17
|
+
- Do **not** use Application ID.
|
|
18
|
+
- Do **not** use the OAuth client secret.
|
|
19
|
+
5. On the same Bot page, enable **Message Content Intent**. Save changes.
|
|
20
|
+
6. Left sidebar → **General Information**. Copy **Application ID** (digits only). That is `DISCORD_APPLICATION_ID`.
|
|
21
|
+
7. In Discord, create or pick a private staff channel. Copy its channel ID (Developer Mode → right-click the channel → Copy Channel ID).
|
|
22
|
+
|
|
23
|
+
## 2. Install once on the machine that will do the work
|
|
24
|
+
|
|
25
|
+
Python 3.11+. From a clone of `dev` today (`pip install discord-os` after PyPI):
|
|
26
|
+
|
|
27
|
+
```bash
|
|
28
|
+
python3 -m venv .venv
|
|
29
|
+
source .venv/bin/activate # Windows: .venv\Scripts\activate
|
|
30
|
+
pip install -e .
|
|
31
|
+
pip install puppetmaster-ai # compute kernel; not bundled
|
|
32
|
+
export OPENROUTER_API_KEY=... # never commit
|
|
33
|
+
discord-os bootstrap
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
Put these in `.env` (or write the bot token to `~/.pmharness/.discord_token`, mode 0600):
|
|
37
|
+
|
|
38
|
+
```bash
|
|
39
|
+
DISCORD_BOT_TOKEN=xxx.yyy.zzz
|
|
40
|
+
DISCORD_APPLICATION_ID=123456789012345678
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
Then one command:
|
|
44
|
+
|
|
45
|
+
```bash
|
|
46
|
+
discord-os setup --channel-id YOUR_CHANNEL_ID
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
That invites the bot (open the printed URL), installs a login helper so it comes back after reboot, and posts a HOST card in the channel with **On** and **Off** buttons. You do not type commands in Discord after this.
|
|
50
|
+
|
|
51
|
+
## 3. Use it from Discord
|
|
52
|
+
|
|
53
|
+
| In Discord | What happens |
|
|
54
|
+
|---|---|
|
|
55
|
+
| **On** | Starts work on the host. Type a normal sentence as a task. |
|
|
56
|
+
| **Off** | Stops work. The helper stays so On still works from your phone. |
|
|
57
|
+
| a normal sentence | A task, only while On. |
|
|
58
|
+
|
|
59
|
+
If Discord logs the bot out, work stops. The login helper starts again idle.
|
|
60
|
+
|
|
61
|
+
No Docker. No slash commands. No public URL. No `/on` to remember.
|
|
62
|
+
|
|
63
|
+
Default I/O is Discord REST. SaseQ/BrainDAO MCP is optional if you already run those servers.
|
|
64
|
+
|
|
65
|
+
## Thesis
|
|
66
|
+
|
|
67
|
+
Discord already is the phone UI, the ACL (a channel you control), the notification bus, and the identity layer. Discord OS treats **artifacts as Discord objects**:
|
|
68
|
+
|
|
69
|
+
```text
|
|
70
|
+
object bytes
|
|
71
|
+
→ attachment + caption on a channel message
|
|
72
|
+
→ Discord CDN (ephemeral signed URL, ~24h)
|
|
73
|
+
→ durable key = channel_id / message_id / attachment_id (+ sha256)
|
|
74
|
+
→ retrieve later by re-fetching the message (fresh URL), then downloading
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
Never persist a CDN URL as the durable key. Official retrieve is Get Channel Message (or `POST /attachments/refresh-urls`). Jump links (`https://discord.com/channels/{guild|@me}/{channel}/{message}`) are what we print for humans.
|
|
78
|
+
|
|
79
|
+
## Prior art (steal economics, not product)
|
|
80
|
+
|
|
81
|
+
Two piles exist. We are neither.
|
|
82
|
+
|
|
83
|
+
| Pile | Examples | What they do | What we take | What we refuse |
|
|
84
|
+
|------|----------|--------------|--------------|----------------|
|
|
85
|
+
| Discord-as-disk | DiscordFS, forscht/ddrive, KITdt/discord-drive, missuo/discord-image | Chunk files into channels as free S3 / WebDav | Snowflake ID pointer; refresh-on-get | Unlimited chunked S3, public CDN, WebDav, 4TB multipart |
|
|
86
|
+
| Discord-as-agent-relay | Discode, Agent4Discord, Agentboard, cursor-mobile-bridge, claudecode-discord | Channel=workspace, thread=run, phone UI; artifacts stay on local disk/tmux | Channel as workspace / ACL; phone-native receipts | Relaying progress while leaving blobs on disk only |
|
|
87
|
+
|
|
88
|
+
Discord has said: if you host files on Discord, find a more suitable service. Honest S3 replacement here means **agent artifacts under Discord size limits in your own staff channel**. It does not mean a public CDN.
|
|
89
|
+
|
|
90
|
+
Default live I/O is **Discord REST** with the Bot token (`DISCORD_BOT_TOKEN` or `~/.pmharness/.discord_token`). That value is the Bot token (`xxx.yyy.zzz`) from the Bot tab — not the Application client ID and not the OAuth client secret. Optional SaseQ/BrainDAO MCP adapters try file-tool names first; if the catalog has no file tool they fall back to the same REST path. We still refuse to base64-dump files into `send_message`. The fake provider is the hermetic proof of the protocol. Foreground `listen` does **not** open a Discord Gateway. `host` / `setup` opens a Gateway **only** so On/Off buttons work — no public URL. Do not run that beside another bot process that already owns the Gateway.
|
|
91
|
+
|
|
92
|
+
## Honest limits
|
|
93
|
+
|
|
94
|
+
- Default object cap is **10 MiB** (`DISCORD_MAX_OBJECT_BYTES=10485760`). Discord free is roughly 10–25MB; Nitro is higher. Configurable, not unlimited.
|
|
95
|
+
- Discord ToS: conversation artifacts in **your** server, not a public CDN or anonymous disk.
|
|
96
|
+
- Not a compute host. Default compute is `AGENT_DISCORD_COMPUTE=auto`: Puppetmaster **agentic** (`openrouter/auto`) when an OpenRouter key is on the host or in the workspace vault; otherwise the Cursor pin (`cursor/grok-4-5` / adapter `grok-4.5`). **No silent model fallback.**
|
|
97
|
+
- No DiscordFS-style multipart chunking. Oversize artifacts become an `overflow` pointer + local stash, not multipart CDN objects.
|
|
98
|
+
- `/connect <secret>` and `/open` are message-prefix verbs by default, not Discord slash Interactions. Discord still sees shred payloads once before delete.
|
|
99
|
+
- `listen` ignores channel history older than a durable per-channel SQLite watermark (first listen: now minus 15s, same slack as process start; later processes resume from the stored high-water, including skipped cards/connects/opens) so a seeded staff channel is not dispatched as an implement job.
|
|
100
|
+
|
|
101
|
+
## What a run does
|
|
102
|
+
|
|
103
|
+
1. **Connect** (optional): `/connect` on the listen host inherits `OPENROUTER_API_KEY`, shreds a pasted secret after delete, or mints a pairing ticket for `discord-os connect --ticket`.
|
|
104
|
+
2. **Intake** a natural-language task from `run` or the host loop (staff channel / phone). Cards, receipts, and object-store captions are skipped. On/Off buttons, `/connect`, and `/open` are intercepted before task dispatch. Work is accepted only while On. Discord is the remote; this process opens Terminal, the file manager, or an allowlisted browser on the host.
|
|
105
|
+
3. **Snapshot** scoped context from SQLite memory + channel bindings.
|
|
106
|
+
4. **Dispatch** to Puppetmaster agentic (OpenRouter/BYOK) or the Cursor pin, depending on resolved compute.
|
|
107
|
+
5. **Persist** events, then **put** backend file artifacts through the object store (overflow pointer + stash when over the cap). Local path is kept if put fails.
|
|
108
|
+
6. **Relay** Discord-safe `**Card**` progress (edited in place when possible) and a receipt that shows kind + jump URL (never a CDN URL, never hidden chain-of-thought).
|
|
109
|
+
|
|
110
|
+
## Optional MCP Discord servers
|
|
111
|
+
|
|
112
|
+
Default I/O is REST. These adapters are optional. **Upstream source code is not copied.**
|
|
113
|
+
|
|
114
|
+
| Provider | Repository | License | Notes |
|
|
115
|
+
|----------|------------|---------|-------|
|
|
116
|
+
| **SaseQ / discord-mcp** | https://github.com/SaseQ/discord-mcp | MIT | HTTP endpoint convention via `SASEQ_MCP_HTTP_URL` (default `http://127.0.0.1:8085/mcp`). Prefer HTTP; stdio requires an explicit `DISCORD_MCP_STDIO_COMMAND` (no fabricated npm default). |
|
|
117
|
+
| **BrainDAO / mcp-discord** (`@iqai`) | https://github.com/BrainDAO/mcp-discord | MIT | Sampling/tool convention exposed as an adapter seam; **one Gateway owner per bot token** — no second Gateway required. Stdio requires an explicit `DISCORD_MCP_STDIO_COMMAND` such as `npx -y @iqai/mcp-discord`. |
|
|
118
|
+
|
|
119
|
+
See [`THIRD_PARTY_NOTICES.md`](THIRD_PARTY_NOTICES.md) for details.
|
|
120
|
+
|
|
121
|
+
## Dev / fake path
|
|
122
|
+
|
|
123
|
+
```bash
|
|
124
|
+
# Requires Python 3.11+
|
|
125
|
+
python -m venv .venv
|
|
126
|
+
source .venv/bin/activate
|
|
127
|
+
pip install -e ".[dev]"
|
|
128
|
+
|
|
129
|
+
cp .env.example .env
|
|
130
|
+
# DISCORD_BOT_TOKEN = Bot token (xxx.yyy.zzz), never Application ID or OAuth secret
|
|
131
|
+
|
|
132
|
+
discord-os bootstrap
|
|
133
|
+
discord-os check
|
|
134
|
+
|
|
135
|
+
# Dry-run (fake Discord + fake Puppetmaster — no network)
|
|
136
|
+
discord-os run "Summarize open items" --channel-id 123 --fake --no-discord-post
|
|
137
|
+
discord-os put ./notes.bin --channel-id 123 --fake --json
|
|
138
|
+
discord-os get MESSAGE_ID --channel-id 123 --out ./got.bin --fake --json
|
|
139
|
+
discord-os listen --channel-id 123 --fake --once
|
|
140
|
+
discord-os connect --provider openrouter --from-env --json
|
|
141
|
+
discord-os status --json
|
|
142
|
+
```
|
|
143
|
+
|
|
144
|
+
### Discord providers
|
|
145
|
+
|
|
146
|
+
- Default: `DISCORD_MCP_PROVIDER=rest` (official API, no MCP process)
|
|
147
|
+
- Optional: `saseq` or `braindao` plus `DISCORD_MCP_TRANSPORT=http` or `stdio`
|
|
148
|
+
- HTTP URLs: `SASEQ_MCP_HTTP_URL` (default `http://127.0.0.1:8085/mcp`) / `BRAINDAO_MCP_HTTP_URL`
|
|
149
|
+
- Stdio: `DISCORD_MCP_STDIO_COMMAND` is **required** when `transport=stdio`
|
|
150
|
+
- `DISCORD_MAX_OBJECT_BYTES` (optional; default `10485760`)
|
|
151
|
+
|
|
152
|
+
MCP catalog discovery is runtime-only. Missing file tools fall back to REST. CDN URLs stay ephemeral.
|
|
153
|
+
|
|
154
|
+
### Compute and keys
|
|
155
|
+
|
|
156
|
+
| Path | How the key arrives | What Discord sees |
|
|
157
|
+
|------|---------------------|-------------------|
|
|
158
|
+
| Host inherit | `OPENROUTER_API_KEY` already on the listen host; `/connect` or `connect --from-env` | Fingerprint only |
|
|
159
|
+
| Pairing ticket | `/connect` with no secret mints an 8-char ticket (15 min); paste the key on host stdin | Ticket code only |
|
|
160
|
+
| Shred absorb | `/connect <secret>` or `!connect <secret>` | Payload once, then delete; card is fingerprint only |
|
|
161
|
+
|
|
162
|
+
Vault files live under `{workspace}/keys/` (ciphertext + `master.key`). Never commit them. Agentic dispatch injects the key into the **subprocess env** as `OPENROUTER_API_KEY` — never argv, never logs.
|
|
163
|
+
|
|
164
|
+
### Host surfaces (poverty default)
|
|
165
|
+
|
|
166
|
+
Discord is the remote. The listen host opens local surfaces the same way a desktop harness does:
|
|
167
|
+
|
|
168
|
+
| Verb | What opens |
|
|
169
|
+
|------|------------|
|
|
170
|
+
| `/open` or `!open` (default: files) | File manager at the workspace-relative path |
|
|
171
|
+
| `/open terminal [path]` | Terminal at that path |
|
|
172
|
+
| `/open files [path]` | File manager |
|
|
173
|
+
| `/open browser <url>` or `/open https://…` | Browser, allowlisted only |
|
|
174
|
+
|
|
175
|
+
Paths stay inside `PUPPETMASTER_CWD` and the workspace. `~` is rejected. Browser URLs are loopback `http(s)` to `127.0.0.1` or `localhost`, or Discord channel jump links (`https://discord.com/channels/…`). Same engine: `discord-os open terminal|files|browser`.
|
|
176
|
+
|
|
177
|
+
Slash chrome is **opt-in** and does not replace `listen`. Set `AGENT_DISCORD_INTERACTIONS=http`, install `pip install 'discord-os[interactions]'` for Ed25519 verify, then `discord-os interactions --register --guild-id ID` and `--serve`. Bind is loopback (`127.0.0.1:8743`). If you want Discord to POST Interactions, you tunnel that URL and paste **your** public HTTPS URL into Developer Portal → Interactions Endpoint URL. Do not paste a tunnel URL into chat. Slash `/connect` has **no secret option** — inherit, ticket, or host CLI only. This does not open a second Gateway.
|
|
178
|
+
|
|
179
|
+
### Puppetmaster model pin
|
|
180
|
+
|
|
181
|
+
| Field | Value |
|
|
182
|
+
|-------|-------|
|
|
183
|
+
| Compute default | `AGENT_DISCORD_COMPUTE=auto` |
|
|
184
|
+
| Agentic canonical / adapter | `openrouter/auto` |
|
|
185
|
+
| Canonical Cursor model (receipts/audit) | `cursor/grok-4-5` |
|
|
186
|
+
| Cursor adapter (`puppetmaster cursor --model`) | `grok-4.5` |
|
|
187
|
+
| Cursor allowlist | **only** `cursor/grok-4-5` |
|
|
188
|
+
| Agentic allowlist | **only** `openrouter/auto` |
|
|
189
|
+
|
|
190
|
+
Requests for any other model raise an error. There is **no** silent remap. Cursor compute still invokes `puppetmaster cursor …`. Agentic compute invokes `puppetmaster agentic … --provider openrouter --mode implement`. Set `PUPPETMASTER_CWD` to control `--cwd`.
|
|
191
|
+
|
|
192
|
+
### Optional Marionette backend
|
|
193
|
+
|
|
194
|
+
Default `AGENT_DISCORD_BACKEND=puppetmaster`. To opt in:
|
|
195
|
+
|
|
196
|
+
```bash
|
|
197
|
+
AGENT_DISCORD_BACKEND=marionette
|
|
198
|
+
MARIONETTE_BASE_URL=http://127.0.0.1:8787 # your local Marionette HTTP API
|
|
199
|
+
# Optional path overrides (defaults shown):
|
|
200
|
+
# MARIONETTE_SESSIONS_PATH=/v1/sessions
|
|
201
|
+
# MARIONETTE_JOBS_PATH=/v1/jobs
|
|
202
|
+
```
|
|
203
|
+
|
|
204
|
+
The adapter documents an expected session/job/events/status/cancel contract; it does **not** pretend an unverified endpoint is guaranteed. Missing `MARIONETTE_BASE_URL` or transport failures surface as configuration/transport errors.
|
|
205
|
+
|
|
206
|
+
## CLI
|
|
207
|
+
|
|
208
|
+
```text
|
|
209
|
+
discord-os bootstrap [--workspace PATH]
|
|
210
|
+
discord-os check [--allow-empty-token] [--live] [--channel-id ID]
|
|
211
|
+
discord-os run TASK --channel-id ID [--message-id ID] [--fake] [--no-discord-post] [--json]
|
|
212
|
+
discord-os setup --channel-id ID
|
|
213
|
+
discord-os host start --channel-id ID
|
|
214
|
+
discord-os host stop
|
|
215
|
+
discord-os host status
|
|
216
|
+
discord-os listen --channel-id ID [--once] [--interval SEC] [--fake] [--json]
|
|
217
|
+
discord-os connect [--provider openrouter] [--ticket T] [--from-env] [--json]
|
|
218
|
+
discord-os status [--json]
|
|
219
|
+
discord-os invite [--application-id ID] [--json]
|
|
220
|
+
discord-os open {terminal,files,browser} [PATH_OR_URL] [--json]
|
|
221
|
+
discord-os interactions [--register] [--guild-id ID] [--serve] [--json]
|
|
222
|
+
discord-os put PATH --channel-id ID [--thread-id ID] [--guild-id ID] [--kind blob] [--fake] [--json]
|
|
223
|
+
discord-os get MESSAGE_ID --channel-id ID [--attachment-id ID] [--out PATH] [--fake] [--json]
|
|
224
|
+
discord-os ls --channel-id ID [--run-id ID] [--fake] [--json]
|
|
225
|
+
```
|
|
226
|
+
|
|
227
|
+
`put` / `get` / `ls --fake` need no network. `get` writes bytes to `--out`, or to stdout only when stdout is not a tty (otherwise `--out` is required). Pointer JSON never includes a `url` key.
|
|
228
|
+
|
|
229
|
+
Also: `python -m agent_discord …`
|
|
230
|
+
|
|
231
|
+
## Architecture (small & readable)
|
|
232
|
+
|
|
233
|
+
```text
|
|
234
|
+
CLI → Orchestrator → backend (Puppetmaster agentic | Puppetmaster cursor | optional Marionette HTTP | fake)
|
|
235
|
+
↘ SQLite (bindings, tasks, runs, events, memory, artifacts + object pointers,
|
|
236
|
+
inbound message dedupe, gateway ownership,
|
|
237
|
+
optional research claims / leases / negatives)
|
|
238
|
+
↘ Discord facade → object store → REST (default) | optional SaseQ/BrainDAO | fake
|
|
239
|
+
```
|
|
240
|
+
|
|
241
|
+
Message intake is REST. The host process opens a Discord Gateway only for On/Off buttons. The SQLite gateway row is a local one-process lock and is stolen if the previous owner pid is dead.
|
|
242
|
+
|
|
243
|
+
- **stdlib-first** core; optional `pytest` for development.
|
|
244
|
+
- Explicit typed contracts + dependency injection — tests never need Discord, Cursor, or network.
|
|
245
|
+
- Durable object key is `DiscordObjectRef` (channel / message / attachment / sha256). Channel id is the ACL; `get` refuses a mismatched caller channel (confused-deputy).
|
|
246
|
+
- Gateway exclusivity is **durable** in SQLite across concurrent local processes (in-memory registry retained for unit tests).
|
|
247
|
+
- Inbound Discord message IDs are deduplicated at orchestration level (prior receipt reused or explicit ignored-duplicate result).
|
|
248
|
+
- Event/artifact payloads recursively strip forbidden hidden-reasoning keys.
|
|
249
|
+
- BrainDAO sampling-compatible ingress is an adapter seam on the same facade.
|
|
250
|
+
- **Research memory** (optional orchestration context): typed claims with deterministic fingerprints, atomic leases, provenance/evidence, and queryable negative findings. Ordinary memory recall is unchanged; research metadata is not required for normal tasks.
|
|
251
|
+
- **Marionette backend** (optional, explicit opt-in via `AGENT_DISCORD_BACKEND=marionette`): stdlib urllib adapter with configurable endpoint paths and injectable transport for tests. Default remains Puppetmaster. Unconfigured/unavailable Marionette fails closed — no silent fallback. Model field still carries the canonical pin `cursor/grok-4-5` → adapter `grok-4.5`.
|
|
252
|
+
|
|
253
|
+
## Development / tests
|
|
254
|
+
|
|
255
|
+
```bash
|
|
256
|
+
pip install -e ".[dev]"
|
|
257
|
+
pytest
|
|
258
|
+
```
|
|
259
|
+
|
|
260
|
+
Tests use fake MCP, Puppetmaster, and Marionette providers only.
|
|
261
|
+
|
|
262
|
+
## License
|
|
263
|
+
|
|
264
|
+
MIT — see [`LICENSE`](LICENSE).
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["setuptools>=68", "wheel"]
|
|
3
|
+
build-backend = "setuptools.build_meta"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "discord-os"
|
|
7
|
+
version = "0.3.0"
|
|
8
|
+
description = "Discord OS: Discord is the harness UI for local agent work. Artifacts are Discord objects (snowflake IDs)."
|
|
9
|
+
readme = "README.md"
|
|
10
|
+
license = { text = "MIT" }
|
|
11
|
+
requires-python = ">=3.11"
|
|
12
|
+
authors = [{ name = "Discord OS contributors" }]
|
|
13
|
+
keywords = ["discord", "discord-os", "mcp", "puppetmaster", "agent", "cli"]
|
|
14
|
+
classifiers = [
|
|
15
|
+
"Development Status :: 3 - Alpha",
|
|
16
|
+
"Environment :: Console",
|
|
17
|
+
"Intended Audience :: Developers",
|
|
18
|
+
"License :: OSI Approved :: MIT License",
|
|
19
|
+
"Programming Language :: Python :: 3",
|
|
20
|
+
"Programming Language :: Python :: 3.11",
|
|
21
|
+
"Programming Language :: Python :: 3.12",
|
|
22
|
+
"Topic :: Communications :: Chat",
|
|
23
|
+
]
|
|
24
|
+
dependencies = []
|
|
25
|
+
|
|
26
|
+
[project.optional-dependencies]
|
|
27
|
+
dev = ["pytest>=8.0"]
|
|
28
|
+
interactions = ["PyNaCl>=1.5"]
|
|
29
|
+
|
|
30
|
+
[project.scripts]
|
|
31
|
+
discord-os = "agent_discord.cli:main"
|
|
32
|
+
agent-discord = "agent_discord.cli:main"
|
|
33
|
+
|
|
34
|
+
[project.urls]
|
|
35
|
+
Homepage = "https://github.com/professorpalmer/agent-discord"
|
|
36
|
+
Attribution-SaseQ = "https://github.com/SaseQ/discord-mcp"
|
|
37
|
+
Attribution-BrainDAO = "https://github.com/BrainDAO/mcp-discord"
|
|
38
|
+
|
|
39
|
+
[tool.setuptools.packages.find]
|
|
40
|
+
where = ["src"]
|
|
41
|
+
|
|
42
|
+
[tool.pytest.ini_options]
|
|
43
|
+
testpaths = ["tests"]
|
|
44
|
+
pythonpath = ["src"]
|
|
45
|
+
addopts = "-q"
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
"""Discord OS: Discord is the harness UI for local agent work."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
PRODUCT_NAME = "Discord OS"
|
|
6
|
+
CLI_NAME = "discord-os"
|
|
7
|
+
PACKAGE_NAME = "discord-os"
|
|
8
|
+
REPO_URL = "https://github.com/professorpalmer/agent-discord"
|
|
9
|
+
CLI_OWNER_PREFIX = "discord-os-cli-"
|
|
10
|
+
LEGACY_CLI_OWNER_PREFIX = "agent-discord-cli-"
|
|
11
|
+
|
|
12
|
+
__version__ = "0.3.0"
|