Discord OMP plugin blueprint
Session ID: 019ea972-29da-7000-9f3b-327723e8c7ca
Date: 2026-06-08 22:54:51
Associated Memory Note: 019ea972-29da-7000-9f3b-327723e8c7ca
Telemetry Stats
| Metric | Value |
|---|---|
| User Prompt CWD | /home/dev |
| Duration | 91.15s |
| Total Message Events | 13 |
| Input / Output Tokens | 60192 / 3971 |
| Cache Read / Write | 104773 / 0 |
| Estimated Cost | $0.000000 |
Chat Transcript
π€ User
https://llm-wiki.0rk.de/topics/discord-omp-plugin/ Create a cheat-sheet style focused, dense blueprint: laying out entire stack, architecture, ui/ux, data model, routes, integration, and rollout phases. https://llm-wiki.0rk.de/topics/discord-omp-plugin-bp/
π€ Assistant
Thinking Process
The user wants me to create a new page at
https://llm-wiki.0rk.de/topics/discord-omp-plugin-bp/that serves as a dense blueprint companion to the existing cheat sheet at/topics/discord-omp-plugin/. This blueprint should cover:
- Entire stack
- Architecture
- UI/UX
- Data model
- Routes
- Integration
- Rollout phases
Let me first read the existing cheat sheet to understand whatβs already covered, then create the blueprint page.
Let me start by reading the existing content and understanding the site structure.
π€ Toolresult
Remaining items (8):
- Read existing cheat sheet content [in_progress] (Research)
- Read llm-wiki site config and structure [pending] (Research)
- Read topics _index.md for nav patterns [pending] (Research)
- Draft discord-omp-plugin-bp blueprint page [pending] (Author)
- Write file to /home/dv/hugo-llm-wiki/content/topics/ [pending] (Author)
- Fix ownership and rebuild Hugo site [pending] (Publish)
- Verify live page returns 200 OK [pending] (Publish)
- Add blueprint link to topics index [pending] (Polish)
Phase 1/4 βResearchβ β 0/3 tasks complete
Research:
β Read existing cheat sheet content
β Read llm-wiki site config and structure
β Read topics _index.md for nav patterns
Author:
β Draft discord-omp-plugin-bp blueprint page
β Write file to /home/dv/hugo-llm-wiki/content/topics/
Publish:
β Fix ownership and rebuild Hugo site
β Verify live page returns 200 OK
Polish:
β Add blueprint link to topics index
π€ Assistant
π€ Toolresult
[/home/dv/hugo-llm-wiki/content/topics/_index.md#6003]
1:---
2:title: βTopicsβ
3:date: 2026-05-29
4:---
5:
6:Knowledge base topics auto-discovered and curated through research pipelines. Fed by JSON API.
7:
8:## Svelte 5 Topics (New)
9:
10:- Svelte 5 Best Practices β Runes, derived, $effect, snippets, {@attach}, performance patterns
11:- Svelte 5 Migration Guide β Codemod, incremental migration, slotsβsnippets, events, dispatchβcallbacks
12:
13:## Core Topics
14:
15:- Hugo Documentation Automation β JSON feeds, zero-build dashboard, Svelte 5 islands, taxonomy gotchas
16:- LLM-Powered Knowledge Bases β Self-discovering docs, RAG, structured LLM output, Svelte 5 interactive layer
17:- Self-Discovering Documentation β Autonomous research loop, topic discovery, quality signals, Svelte 5 TopicExplorer
18:- AI Content Evolution β Scheduled scans, freshness tracking, incremental updates, Svelte 5 evolution dashboard
π€ Toolresult
[/home/dv/hugo-llm-wiki/config.toml#0764]
1:baseURL = βhttps://llm-wiki.0rk.deβ
2:languageCode = βen-usβ
3:title = βLLM Wikiβ
4:theme = βlotusdocsβ
5:enableRobotsTXT = true
6:enableGitInfo = false
7:enableEmoji = true
8:
9:[params]
10: description = βSelf-discovering documentation + system reference. Hugo static site powered by AI research pipelines.β
11: version = β0.6.0β
12: lastUpdated = true
13: displayUpdatedDate = true
14: showReadingTime = true
15: showWordCount = true
16: showTableOfContents = true
17: tocLevel = 3
18:
19:[menu]
20: menu.main
21: name = βHomeβ
22: weight = 10
23: url = β/β
24: menu.main
25: name = βArchitectureβ
26: weight = 12
27: url = β/architecture/β
28: menu.main
29: name = βReferenceβ
30: weight = 15
31: url = β/llm-wiki-reference/β
32: menu.main
33: name = βTopicsβ
34: weight = 20
35: url = β/topics/β
36: menu.main
37: name = βResearchβ
38: weight = 30
39: url = β/research/β
40: menu.main
41: name = βEvolvingβ
42: weight = 40
43: url = β/evolving/β
44: menu.main
45: name = βSearchβ
46: weight = 50
47: url = β/search/β
48:
49:[taxonomies]
50: tag = βtagsβ
51: series = βseriesβ
52:
53:[outputs]
54: home = [βHTMLβ, βRSSβ, βJSONβ]
55: page = [βHTMLβ, βJSONβ]
56: section = [βHTMLβ, βRSSβ, βJSONβ]
57:
58:[outputFormats.JSON]
59: mediaType = βapplication/jsonβ
60: baseName = βindexβ
61: isPlainText = true
62:
63:[outputFormats.RSS]
64: mediaType = βapplication/rss+xmlβ
65: baseName = βindexβ
66:
67:[sitemap]
68: changefreq = βdailyβ
69: priority = 0.7
70: filename = βsitemap.xmlβ
71:
72:[markup]
73: [markup.goldmark]
74: [markup.goldmark.renderer]
75: unsafe = true
76: [markup.highlight]
77: codeFences = true
78: guessSyntax = true
79: hl_Lines = ""
80: lineNoStart = 1
81: lineNos = true
82: lineNumbersInTable = true
83: noClasses = false
84: style = βgithub-darkβ
85: tabWidth = 2
86: [markup.tableOfContents]
87: endLevel = 3
88: ordered = false
89: startLevel = 2
π€ Toolresult
[/home/dv/hugo-llm-wiki/content/topics/discord-omp-plugin.md#FBEB]
1:---
2:title: βπ€ Discord.py Γ OMP Plugin Cheat Sheetβ
3:date: 2026-06-08T00:00:00+00:00
4:draft: false
5:topic_type: βtopicsβ
6:tags: [βdiscordβ, βompβ, βpluginβ, βmcpβ, βextensionβ, βdiscord.pyβ, βcheatsheetβ]
7:---
8:
9:# π€ Discord.py Γ OMP Plugin Cheat Sheet
10:
11:> Bridge a discord.py bot (Python) with the OMP coding agent extension system (Bun/TS) β from zero to full bidirectional integration.
12:
13:---
14:
15:## π§ Core Concept
16:
17:OMP extensions = TypeScript/Bun. discord.py = Python. Theyβre separate runtimes. You bridge them via one of three tiers:
18:
19:| | πͺ Tier 1: Webhook | π§ Tier 2: MCP Server | π Tier 3: Extension + Bot |
20:|:---|:---|:---|:---|
21:| Direction | OMP β Discord only | Both (LLM invokes tools) | Both (event-driven) |
22:| Needs Python? | β No | β
Yes | β
Yes |
23:| Needs Bot Token? | β No (webhook URL) | β
Yes | β
Yes |
24:| Discord β OMP? | β | β | β
|
25:| Auto-notify events? | β
| β | β
|
26:| LLM calls Discord? | β | β
| β
|
27:| Setup time | ~5 min | ~15 min | ~30β60 min |
28:| Deps | None | discord.py, mcp | discord.py, aiohttp |
29:
30:---
31:
32:## β‘ OMP Extension Contract (All Tiers)
33:
34:typescript 35:// ~/.omp/agent/extensions/my-ext/index.ts 36:import type { ExtensionAPI } from "@oh-my-pi/pi-coding-agent"; 37: 38:export default function myExt(pi: ExtensionAPI) { 39: // ββ Registration Phase (sync) ββ 40: pi.setLabel("My Extension"); 41: 42: pi.on("agent_end", async (event, ctx) => { /* ... */ }); 43: pi.on("session_start", async (event, ctx) => { /* ... */ }); 44: 45: pi.registerTool({ name: "my_tool", /* ... */ }); 46: pi.registerCommand("my-cmd", { /* ... */ }); 47: 48: // β οΈ CANNOT call pi.sendMessage() during load β only in handlers 49:} 50:
51:
52:# π‘ Key Lifecycle Events
53:
54:text 55:session_start β input β agent_start β turn_start 56: β tool_call β tool_execution_start β tool_execution_end β tool_result 57: β turn_end β agent_end β session_shutdown 58:
59:
60:| Event | Use Case | Return |
61:|:---|:---|:---|
62:| agent_end | βDoneβ notification to Discord | β |
63:| tool_call | Audit / block dangerous ops | { block, reason } |
64:| tool_result | Redact secrets before logging | { content } |
65:| session_start | Init bot connection | β |
66:| session_shutdown | Cleanup / final flush | β |
67:
68:---
69:
70:## πͺ Tier 1 β Webhook (Zero-Dep, 5 min)
71:
72:No bot. No Python. Just fetch(). OMP β Discord one-way.
73:
74:# π ~/.omp/agent/extensions/discord-notify/index.ts
75:
76:typescript 77:import type { ExtensionAPI } from "@oh-my-pi/pi-coding-agent"; 78: 79:export default function discordNotify(pi: ExtensionAPI) { 80: const WEBHOOK = process.env.DISCORD_WEBHOOK_URL; 81: if (!WEBHOOK) return; 82: 83: const send = (content: string) => 84: fetch(WEBHOOK, { 85: method: "POST", 86: headers: { "Content-Type": "application/json" }, 87: body: JSON.stringify({ content }), 88: }).catch(() => {}); 89: 90: // π Auto-notify when agent finishes 91: pi.on("agent_end", async () => { 92: await send(`β
**Agent finished** β ${new Date().toLocaleTimeString()}`); 93: }); 94: 95: // π¬ Slash command: /discord-ping <message> 96: pi.registerCommand("discord-ping", { 97: description: "Send a message to the Discord webhook", 98: handler: async (args, ctx) => { 99: await send(args.trim() || "Ping from OMP!"); 100: ctx.ui.notify("Sent to Discord", "info"); 101: }, 102: }); 103:} 104:
105:
106:# π Setup
107:
108:bash 109:# 1. Create webhook: Discord β Server Settings β Integrations β Webhooks β New 110:# 2. Export URL 111:export DISCORD_WEBHOOK_URL="https://discord.com/api/webhooks/123/abc..." 112: 113:# 3. Drop file, restart OMP. Done. 114:
115:
116:> β οΈ Rate limit: Discord enforces 5 requests / 5 seconds per webhook.
117:
118:---
119:
120:## π§ Tier 2 β MCP Server (Bot as Tool Provider)
121:
122:discord.py bot runs as a stdio MCP server. OMP launches it, the LLM calls its tools.
123:
124:text 125:OMP Agent βββstdio JSON-RPCβββΆ Python MCP Server βββgatewayβββΆ Discord 126:
127:
128:# π ~/.omp/tools/discord-mcp/server.py
129:
130:python 131:#!/usr/bin/env python3 132:"""Discord bot exposed as MCP tool server.""" 133:import asyncio, os 134:import discord 135:from discord.ext import commands 136:from mcp.server import Server 137:from mcp.server.stdio import stdio_server 138:import mcp.types as types 139: 140:# ββ Bot ββ 141:intents = discord.Intents.default() 142:intents.message_content = True 143:bot = commands.Bot(command_prefix="!", intents=intents) 144:ready = asyncio.Event() 145: 146:@bot.event 147:async def on_ready(): 148: ready.set() 149: 150:# ββ MCP Server ββ 151:app = Server("discord-mcp") 152: 153:@app.list_tools() 154:async def list_tools() -> list[types.Tool]: 155: return [ 156: types.Tool( 157: name="discord_send", 158: description="Send a message to a Discord channel", 159: inputSchema={ 160: "type": "object", 161: "properties": { 162: "channel_id": {"type": "string"}, 163: "content": {"type": "string"}, 164: }, 165: "required": ["channel_id", "content"], 166: }, 167: ), 168: types.Tool( 169: name="discord_read", 170: description="Read recent messages from a Discord channel", 171: inputSchema={ 172: "type": "object", 173: "properties": { 174: "channel_id": {"type": "string"}, 175: "limit": {"type": "integer", "default": 10}, 176: }, 177: "required": ["channel_id"], 178: }, 179: ), 180: types.Tool( 181: name="discord_guilds", 182: description="List all guilds the bot is in", 183: inputSchema={"type": "object", "properties": {}}, 184: ), 185: ] 186: 187:@app.call_tool() 188:async def call_tool(name: str, arguments: dict) -> list[types.TextContent]: 189: await ready.wait() 190: 191: if name == "discord_send": 192: ch = bot.get_channel(int(arguments["channel_id"])) 193: if not ch: 194: return [types.TextContent(type="text", text="Channel not found")] 195: await ch.send(arguments["content"]) 196: return [types.TextContent(type="text", text=f"Sent to #{ch.name}")] 197: 198: if name == "discord_read": 199: ch = bot.get_channel(int(arguments["channel_id"])) 200: if not ch: 201: return [types.TextContent(type="text", text="Channel not found")] 202: msgs = [] 203: async for m in ch.history(limit=arguments.get("limit", 10)): 204: msgs.append(f"[{m.created_at:%H:%M}] {m.author.display_name}: {m.content}") 205: return [types.TextContent(type="text", text="\n".join(reversed(msgs)) or "(empty)")] 206: 207: if name == "discord_guilds": 208: lines = [f"{g.name} (id={g.id})" for g in bot.guilds] 209: return [types.TextContent(type="text", text="\n".join(lines) or "(none)")] 210: 211: return [types.TextContent(type="text", text=f"Unknown tool: {name}")] 212: 213:async def main(): 214: token = os.environ["DISCORD_TOKEN"] 215: async with bot: 216: bot_task = asyncio.create_task(bot.start(token)) 217: async with stdio_server() as (r, w): 218: await app.run(r, w, app.create_initialization_options()) 219: bot_task.cancel() 220: 221:if __name__ == "__main__": 222: asyncio.run(main()) 223:
224:
225:# π ~/.omp/agent/mcp.json
226:
227:json 228:{ 229: "mcpServers": { 230: "discord": { 231: "command": "python3", 232: "args": ["/home/dev/.omp/tools/discord-mcp/server.py"], 233: "env": { "DISCORD_TOKEN": "DISCORD_TOKEN" } 234: } 235: } 236:} 237:
238:
239:# π¦ Install deps
240:
241:bash 242:pip install discord.py mcp 243:# env value "DISCORD_TOKEN": "DISCORD_TOKEN" = copy from shell env 244:export DISCORD_TOKEN="your-bot-token" 245:# Restart OMP β it auto-discovers mcp.json and launches the server 246:
247:
248:---
249:
250:## π Tier 3 β Full Extension + Sidecar Bot
251:
252:Most powerful. OMP extension (TS) β discord.py bot (Python) over localhost HTTP. Full bidirectional event-driven bridge.
253:
254:text 255:ββββββββββββββββββββββββββββββββββββ 256:β OMP Extension (Bun/TS) β 257:β β on(agent_end) β POST /notify β 258:β β registerTool(discord_notify) β 259:β β registerCommand(/discord) β 260:ββββββββββββ¬ββββββββββββββββββββββββ 261: β HTTP :8901 262:ββββββββββββΌββββββββββββββββββββββββ 263:β discord.py Bot + aiohttp (Py) β 264:β β POST /notify β channel.send() β 265:β β GET /status β bot info β 266:β β GET /channels β list all β 267:β β !ask-omp <prompt> β omp CLI β 268:ββββββββββββ¬ββββββββββββββββββββββββ 269: β Discord Gateway 270:ββββββββββββΌββββββββββββββββββββββββ 271:β Discord β 272:ββββββββββββββββββββββββββββββββββββ 273:
274:
275:# π Python Sidecar: discord_omp_bot.py
276:
277:python 278:#!/usr/bin/env python3 279:"""discord.py sidecar bot with HTTP API for OMP.""" 280:import asyncio, logging, os, subprocess 281:import discord 282:from discord import app_commands 283:from discord.ext import commands 284:from aiohttp import web 285: 286:log = logging.getLogger("discord-omp") 287:intents = discord.Intents.default() 288:intents.message_content = True 289:bot = commands.Bot(command_prefix="!", intents=intents) 290: 291:NOTIFY_CH = int(os.environ.get("DISCORD_NOTIFY_CHANNEL", "0")) 292:HTTP_PORT = int(os.environ.get("DISCORD_HTTP_PORT", "8901")) 293: 294:# ββ HTTP endpoints (called by OMP extension) ββ 295:async def handle_notify(req: web.Request) -> web.Response: 296: data = await req.json() 297: ch = bot.get_channel(NOTIFY_CH) 298: if not ch: 299: return web.json_response({"error": "channel not found"}, status=404) 300: β¦ 364: asyncio.run(bot.start(os.environ["DISCORD_TOKEN"])) 365:
366:
β¦
458:
β¦
642:Last updated: 2026-06-08 Β· Grounded in OMP extension docs, pi-langfuse reference impl, and discord.py v2.x API.
[Showing lines 1-300 of 643. Use :301 to continue]
π€ Assistant
π€ Toolresult
Extensions
Primary guide for authoring runtime extensions in packages/coding-agent.
This document covers the current extension runtime in:
src/extensibility/extensions/types.tssrc/extensibility/extensions/runner.tssrc/extensibility/extensions/wrapper.tssrc/extensibility/extensions/index.tssrc/modes/controllers/extension-ui-controller.ts
For discovery paths and filesystem loading rules, see extension-loading.md.
What an extension is
An extension is a TS/JS module exporting a default factory:
import type { ExtensionAPI } from "@oh-my-pi/pi-coding-agent";
export default function myExtension(pi: ExtensionAPI) {
// register handlers/tools/commands/renderers
}Extensions can combine all of the following in one module:
- event handlers (
pi.on(...)) - LLM-callable tools (
pi.registerTool(...)) - slash commands (
pi.registerCommand(...)) - keyboard shortcuts and flags
- custom message rendering
- session/message injection APIs (
sendMessage,sendUserMessage,appendEntry)
Runtime model
- Extensions are imported and their factory functions run.
- During that load phase, registration methods are valid; runtime action methods are not yet initialized.
ExtensionRunner.initialize(...)wires live actions/contexts for the active mode.- Session/agent/tool lifecycle events are emitted to handlers.
- Every tool execution is wrapped with extension interception (
tool_call/tool_result).
Extension lifecycle (simplified)
load paths
β
βΌ
import module + run factory (registration only)
β
βΌ
ExtensionRunner.initialize(mode/session/tool registry)
β
ββ emit session/agent events to handlers
ββ wrap tool execution (tool_call/tool_result)
ββ expose runtime actions (sendMessage, setActiveTools, ...)Important constraint from loader.ts:
- calling action methods like
pi.sendMessage()during extension load throwsExtensionRuntimeNotInitializedError - register first; perform runtime behavior from events/commands/tools
Quick start
import type { ExtensionAPI } from "@oh-my-pi/pi-coding-agent";
export default function (pi: ExtensionAPI) {
const { z } = pi.zod;
pi.setLabel("Safety + Utilities");
pi.on("session_start", async (_event, ctx) => {
ctx.ui.notify(`Extension loaded in ${ctx.cwd}`, "info");
});
pi.on("tool_call", async (event) => {
if (event.toolName === "bash" && event.input.command?.includes("rm -rf")) {
return { block: true, reason: "Blocked by extension policy" };
}
});
pi.registerTool({
name: "hello_extension",
label: "Hello Extension",
description: "Return a greeting",
parameters: z.object({ name: z.string() }),
async execute(_toolCallId, params, _signal, _onUpdate, _ctx) {
return {
content: [{ type: "text", text: `Hello, ${params.name}` }],
details: { greeted: params.name },
};
},
});
pi.registerCommand("hello-ext", {
description: "Show queue state",
handler: async (_args, ctx) => {
ctx.ui.notify(`pending=${ctx.hasPendingMessages()}`, "info");
},
});
}Extension API surfaces
1) Registration and actions (ExtensionAPI)
Core methods:
on(event, handler)registerTool,registerCommand,registerShortcut,registerFlagregisterMessageRenderer,registerAssistantThinkingRenderersetLabel,getFlagsendMessage,sendUserMessage,appendEntry,execgetActiveTools,getAllTools,setActiveToolsgetCommandsgetSessionName,setSessionNamesetModel,getThinkingLevel,setThinkingLevelregisterProviderevents(shared event bus)
In interactive mode, input handlers run before the built-in first-message auto-title check. Extensions that call await pi.setSessionName(...) from input can set the persisted session name and prevent the default auto-generated title from running for that session.
Also exposed:
pi.loggerpi.typebox(zod-backed compatibility shim for legacy TypeBox-style schemas)pi.zod(injectedzod/v4module β canonical for tool parameter schemas)pi.pi(package exports)
Message delivery semantics
pi.sendMessage(message, options) supports:
deliverAs: "steer"(default) β interrupts current rundeliverAs: "followUp"β queued to run after current rundeliverAs: "nextTurn"β stored and injected on the next user prompttriggerTurn: trueβ starts a turn when idle (nextTurnignores this)
pi.sendUserMessage(content, { deliverAs }) always goes through prompt flow; while streaming it queues as steer/follow-up.
2) Handler context (ExtensionContext)
Handlers and tool execute receive ctx with:
uihasUIcwdsessionManager(read-only)modelRegistry,modelgetContextUsage()compact(...)isIdle(),hasPendingMessages(),abort()shutdown()getSystemPrompt()
3) Command context (ExtensionCommandContext)
Command handlers additionally get:
waitForIdle()newSession(...)switchSession(...)branch(entryId)navigateTree(targetId, { summarize })reload()
Use command context for session-control flows; these methods are intentionally separated from general event handlers.
Event surface (current names and behavior)
Canonical event unions and payload types are in types.ts.
Session lifecycle
session_startsession_before_switch/session_switchsession_before_branch/session_branchsession_before_compact/session.compacting/session_compactsession_before_tree/session_treesession_shutdown
Cancelable pre-events:
session_before_switchβ{ cancel?: boolean }session_before_branchβ{ cancel?: boolean; skipConversationRestore?: boolean }session_before_compactβ{ cancel?: boolean; compaction?: CompactionResult }session_before_treeβ{ cancel?: boolean; summary?: { summary: string; details?: unknown } }
Prompt and turn lifecycle
inputbefore_agent_startbefore_provider_request(may replace provider request payload)after_provider_responsecontextagent_start/agent_endturn_start/turn_endmessage_start/message_update/message_end
Tool lifecycle
tool_call(pre-exec, may block)tool_result(post-exec, may patch content/details/isError)tool_execution_start/tool_execution_update/tool_execution_end(observability)
tool_result is middleware-style: handlers run in extension order and each sees prior modifications.
Reliability/runtime signals
auto_compaction_start/auto_compaction_endauto_retry_start/auto_retry_endttsr_triggeredtodo_remindergoal_updatedcredential_disabled
User command interception
user_bash(override with{ result })user_python(override with{ result })
resources_discover
resources_discover exists in extension types and ExtensionRunner.
Current runtime note: ExtensionRunner.emitResourcesDiscover(...) is implemented, but there are no AgentSession callsites invoking it in the current codebase.
Tool authoring details
registerTool uses ToolDefinition from types.ts.
Current execute signature:
execute(
toolCallId,
params,
signal,
onUpdate,
ctx,
): Promise<AgentToolResult>Template:
const { z } = pi.zod;
pi.registerTool({
name: "my_tool",
label: "My Tool",
description: "...",
parameters: z.object({}),
hidden: false,
defaultInactive: false,
deferrable: false,
async execute(_id, _params, signal, onUpdate, ctx) {
if (signal?.aborted) {
return { content: [{ type: "text", text: "Cancelled" }] };
}
onUpdate?.({ content: [{ type: "text", text: "Working..." }] });
return { content: [{ type: "text", text: "Done" }], details: {} };
},
onSession(event, ctx) {
// reason: start|switch|branch|tree|shutdown
},
renderCall(args, options, theme) {
// optional TUI render
},
renderResult(result, options, theme, args) {
// optional TUI render
},
});tool_call/tool_result intercept all tools once the registry is wrapped in sdk.ts, including built-ins and extension/custom tools. ToolDefinition also supports optional hidden, defaultInactive, deferrable, mcpServerName, mcpToolName, renderCall, and renderResult fields.
UI integration points
ctx.ui implements the ExtensionUIContext interface. Support differs by mode.
Interactive mode (extension-ui-controller.ts)
Supported:
- dialogs:
select,confirm,input,editor - input editing:
setEditorText,getEditorText,pasteToEditor,editor - terminal title and working message (
setTitle,setWorkingMessage) - notifications/status/editor text/terminal input/custom overlays
- theme listing/loading by name (
setThemesupports string names) - tools expanded toggle
Current no-op methods in this controller:
setFootersetHeadersetEditorComponent
Also note: setWidget currently routes to status-line text via setHookWidget(...).
RPC mode (rpc-mode.ts)
ctx.ui is backed by RPC extension_ui_request events:
- dialog methods (
select,confirm,input,editor) round-trip to client responses - fire-and-forget methods emit requests (
notify,setStatus,setWidgetfor string arrays,setTitle,setEditorText)
Unsupported/no-op in RPC implementation:
onTerminalInputcustomsetFooter,setHeader,setEditorComponentsetWorkingMessage- theme switching/loading (
setThemereturns failure) - tool expansion controls are inert
Print/headless/subagent paths
When no UI context is supplied to runner init, ctx.hasUI is false and methods are no-op/default-returning.
Background interactive mode
Background mode installs a non-interactive UI context object. In current implementation, ctx.hasUI may still be true while interactive dialogs return defaults/no-op behavior.
Session and state patterns
For durable extension state:
- Persist with
pi.appendEntry(customType, data). - Rebuild state from
ctx.sessionManager.getBranch()onsession_start,session_branch,session_tree. - Keep tool result
detailsstructured when state should be visible/reconstructible from tool result history.
Example reconstruction pattern:
pi.on("session_start", async (_event, ctx) => {
let latest;
for (const entry of ctx.sessionManager.getBranch()) {
if (entry.type === "custom" && entry.customType === "my-state") {
latest = entry.data;
}
}
// restore from latest
});Rendering extension points
Custom message renderer
pi.registerMessageRenderer("my-type", (message, { expanded }, theme) => {
// return pi-tui Component
});Used by interactive rendering when custom messages are displayed.
Assistant thinking renderer
import { Container, Text } from "@oh-my-pi/pi-tui";
pi.registerAssistantThinkingRenderer((context, theme) => {
const container = new Container();
container.addChild(new Text(theme.fg("dim", `thinking chars: ${context.text.length}`), 1, 0));
return container;
});Used by interactive rendering to add display-only supplemental UI below each visible assistant thinking block. The renderer receives the already-visible thinking text, content/thinking indexes, theme, and a requestRender() callback for async renderers. All registered renderers that return a component are appended in registration order. Renderers must not mutate messages; the original thinking block remains the provider/session source of truth.
Tool call/result renderer
Provide renderCall / renderResult on registerTool definitions for custom tool visualization in TUI.
Constraints and pitfalls
- Runtime actions are unavailable during extension load.
tool_callerrors block execution (fail-closed).- Command name conflicts with built-ins are skipped with diagnostics.
- Reserved shortcuts are ignored (
ctrl+c,ctrl+d,ctrl+z,ctrl+k,ctrl+p,ctrl+l,ctrl+o,ctrl+t,ctrl+g,ctrl+q,alt+m,shift+tab,shift+ctrl+p,alt+enter,escape,enter). - Treat
ctx.reload()as terminal for the current command handler frame.
Extensions vs hooks vs custom-tools
Use the right surface:
- Extensions (
src/extensibility/extensions/*): unified system (events + tools + commands + renderers + provider registration). - Hooks (
src/extensibility/hooks/*): separate legacy event API. - Custom-tools (
src/extensibility/custom-tools/*): tool-focused modules; when loaded alongside extensions they are adapted and still pass through extension interception wrappers.
If you need one package that owns policy, tools, command UX, and rendering together, use extensions.
π€ Toolresult
[/home/dv/hugo-llm-wiki/content/topics/discord-omp-plugin.md#FBEB]
9:# π€ Discord.py Γ OMP Plugin Cheat Sheet
β¦
250:## π Tier 3 β Full Extension + Sidecar Bot
β¦
275:# π Python Sidecar: discord_omp_bot.py
β¦
277:python 278:#!/usr/bin/env python3 β¦ 300: 301: embed_data = data.get("embed") 302: if embed_data: 303: embed = discord.Embed( 304: title=embed_data.get("title", ""), 305: description=embed_data.get("description", ""), 306: color=embed_data.get("color", 0x5865F2), 307: ) 308: for f in embed_data.get("fields", []): 309: embed.add_field( 310: name=f["name"], value=f["value"], inline=f.get("inline", False) 311: ) 312: await ch.send(content=data.get("content"), embed=embed) 313: else: 314: await ch.send(data.get("content", "π¨ (empty)")) 315: return web.json_response({"ok": True}) 316: 317:async def handle_status(_: web.Request) -> web.Response: 318: return web.json_response({ 319: "bot": str(bot.user), "guilds": len(bot.guilds), 320: "latency_ms": round(bot.latency * 1000, 1), "ready": bot.is_ready(), 321: }) 322: 323:async def handle_channels(_: web.Request) -> web.Response: 324: return web.json_response([ 325: {"id": str(c.id), "name": c.name, "guild": g.name} 326: for g in bot.guilds for c in g.text_channels 327: ]) 328: 329:async def start_http(): 330: app = web.Application() 331: app.router.add_post("/notify", handle_notify) 332: app.router.add_get("/status", handle_status) 333: app.router.add_get("/channels", handle_channels) 334: runner = web.AppRunner(app) 335: await runner.setup() 336: await web.TCPSite(runner, "127.0.0.1", HTTP_PORT).start() 337: log.info(f"HTTP API β 127.0.0.1:{HTTP_PORT}") 338: 339:# ββ Discord commands ββ 340:@bot.event 341:async def on_ready(): 342: await start_http() 343: await bot.tree.sync() 344: log.info(f"Ready as {bot.user}") 345: 346:@bot.hybrid_command(name="ask-omp", description="Ask the OMP coding agent") 347:@app_commands.describe(prompt="Your question") 348:async def ask_omp(ctx: commands.Context, *, prompt: str): 349: await ctx.defer() 350: try: 351: r = subprocess.run( 352: ["omp", "-p", prompt, "--no-input"], 353: capture_output=True, text=True, timeout=120, cwd="/home/dev", 354: ) 355: out = r.stdout.strip()[:1900] or "(no output)" 356: await ctx.send(f"\n{out}\n") 357: except subprocess.TimeoutExpired: 358: await ctx.send("β±οΈ Timed out (120s)") 359: except Exception as e: 360: await ctx.send(f"β {e}") 361: 362:if __name__ == "__main__": 363: logging.basicConfig(level=logging.INFO) 364: asyncio.run(bot.start(os.environ["DISCORD_TOKEN"])) 365:
366:
367:# π OMP Extension: ~/.omp/agent/extensions/discord-bridge/index.ts
368:
369:typescript 370:import type { ExtensionAPI } from "@oh-my-pi/pi-coding-agent"; 371: 372:const BOT = process.env.DISCORD_BOT_URL ?? "http://127.0.0.1:8901"; 373: 374:export default function discordBridge(pi: ExtensionAPI) { 375: const { z } = pi.zod; 376: pi.setLabel("Discord Bridge"); 377: 378: // π Auto-notify on agent completion 379: pi.on("agent_end", async (_ev, ctx) => { 380: const tokens = ctx.getContextUsage?.()?.tokens ?? "?"; 381: await fetch(`${BOT}/notify`, { 382: method: "POST", 383: headers: { "Content-Type": "application/json" }, 384: body: JSON.stringify({ 385: embed: { 386: title: "β
Agent Run Complete", 387: description: `Session: ${pi.getSessionName?.() ?? "β"}`, 388: color: 0x00ff00, 389: fields: [{ name: "Tokens", value: `${tokens}`, inline: true }], 390: }, 391: }), 392: }).catch(() => {}); 393: }); 394: 395: // π οΈ LLM-callable tool 396: pi.registerTool({ 397: name: "discord_notify", 398: label: "Discord Notify", 399: description: "Send a notification to the configured Discord channel", 400: parameters: z.object({ 401: message: z.string().describe("Message to send"), 402: }), 403: async execute(_id, params) { 404: const res = await fetch(`${BOT}/notify`, { 405: method: "POST", 406: headers: { "Content-Type": "application/json" }, 407: body: JSON.stringify({ content: params.message }), 408: }); 409: const data = (await res.json()) as { ok?: boolean; error?: string }; 410: return data.ok 411: ? { content: [{ type: "text", text: "β
Sent to Discord" }] } 412: : { content: [{ type: "text", text: `β ${data.error}` }], isError: true }; 413: }, 414: }); 415: 416: // β¨οΈ OMP slash command: /discord 417: pi.registerCommand("discord", { 418: description: "Check Discord bot status", 419: handler: async (_args, ctx) => { 420: try { 421: const s = (await (await fetch(`${BOT}/status`)).json()) as Record<string, unknown>; 422: ctx.ui.notify(`π€ ${s.bot} | ${s.guilds} guilds | ${s.latency_ms}ms`, "info"); 423: } catch { 424: ctx.ui.notify("Bot unreachable", "warning"); 425: } 426: }, 427: }); 428:} 429:
430:
431:# π Deploy the Sidecar as systemd
432:
433:ini 434:# ~/.config/systemd/user/discord-omp-bot.service 435:[Unit] 436:Description=Discord OMP Bridge Bot 437:After=network.target 438: 439:[Service] 440:ExecStart=/usr/bin/python3 /home/dev/discord_omp_bot.py 441:Environment=DISCORD_TOKEN=your-token 442:Environment=DISCORD_NOTIFY_CHANNEL=123456789 443:Environment=DISCORD_HTTP_PORT=8901 444:Restart=on-failure 445:RestartSec=5 446: 447:[Install] 448:WantedBy=default.target 449:
450:
451:bash 452:systemctl --user daemon-reload 453:systemctl --user enable --now discord-omp-bot 454:journalctl --user -u discord-omp-bot -f 455:
456:
457:---
458:
459:## π¦ Packaging as Distributable OMP Plugin
460:
461:# π File Structure
462:
463:text 464:omp-discord-bridge/ 465:βββ index.ts β source 466:βββ package.json 467:βββ dist/ 468: βββ index.js β bundled (generated) 469:
470:
471:# π package.json
472:
473:json 474:{ 475: "name": "omp-discord-bridge", 476: "version": "1.0.0", 477: "type": "module", 478: "main": "dist/index.js", 479: "omp": { "extensions": ["./dist/index.js"] }, 480: "peerDependencies": { 481: "@oh-my-pi/pi-coding-agent": "*" 482: }, 483: "scripts": { 484: "build": "bun build ./index.ts --outdir ./dist --target node --external @oh-my-pi/pi-coding-agent" 485: } 486:} 487:
488:
489:# π¨ Build & Install
490:
491:bash 492:# Bundle (CRITICAL β OMP legacy loader copies to /tmp, breaking relative imports) 493:bun build ./index.ts --outdir ./dist --target node \ 494: --external @oh-my-pi/pi-coding-agent 495: 496:# Install as plugin 497:omp plugin link /path/to/omp-discord-bridge 498: 499:# Verify 500:omp plugin list 501:
502:
503:> π¨ NEVER skip bundling. OMP mirrors extensions to /tmp/omp-legacy-pi-file/ β unbundled relative require() calls break silently. Always --external @oh-my-pi/pi-coding-agent.
504:
505:---
506:
507:## π discord.py Essentials
508:
509:# π Bot Token Setup
510:
511:text 512:1. discord.com/developers/applications β New Application 513:2. Bot tab β Reset Token β π copy 514:3. Bot tab β β
MESSAGE CONTENT Intent 515:4. OAuth2 β URL Generator β bot + applications.commands 516: β Send Messages, Read History, Embed Links, Use Slash Commands 517: β Copy invite URL β open in browser β select server 518:
519:
520:# π§© Intents Quick-Ref
521:
522:python 523:intents = discord.Intents.default() 524:intents.message_content = True # β οΈ Required for prefix commands 525:intents.guilds = True # Guild join/leave events 526:intents.members = True # β οΈ Privileged β needs portal toggle 527:
528:
529:# π Hybrid Commands (Slash + Prefix)
530:
531:python 532:@bot.hybrid_command(name="ping", description="Pong!") 533:async def ping(ctx: commands.Context): 534: await ctx.send(f"π {bot.latency*1000:.0f}ms") 535: 536:# Don't forget: 537:@bot.event 538:async def on_ready(): 539: await bot.tree.sync() # β registers slash commands with Discord 540:
541:
542:# π§± Cog Pattern
543:
544:python 545:class MyCog(commands.Cog): 546: def __init__(self, bot): self.bot = bot 547: async def cog_load(self): ... # setup 548: async def cog_unload(self): ... # teardown 549: 550: @commands.hybrid_command() 551: async def hello(self, ctx): await ctx.send("Hi!") 552: 553:async def setup(bot): # β entry point 554: await bot.add_cog(MyCog(bot)) 555:
556:
557:# β° Background Tasks
558:
559:python 560:from discord.ext import tasks 561: 562:@tasks.loop(minutes=5) 563:async def heartbeat(): 564: ... # periodic work 565: 566:@heartbeat.before_loop 567:async def wait_ready(): 568: await bot.wait_until_ready() 569: 570:heartbeat.start() # call in on_ready or cog_load 571:
572:
573:# π±οΈ Views (Buttons)
574:
575:python 576:class Confirm(discord.ui.View): 577: value: bool | None = None 578: 579: @discord.ui.button(label="β
Yes", style=discord.ButtonStyle.green) 580: async def yes(self, interaction, button): 581: self.value = True 582: await interaction.response.edit_message(content="Confirmed!", view=None) 583: self.stop() 584: 585: @discord.ui.button(label="β No", style=discord.ButtonStyle.red) 586: async def no(self, interaction, button): 587: self.value = False 588: await interaction.response.edit_message(content="Cancelled.", view=None) 589: self.stop() 590: 591:# Usage: 592:view = Confirm(timeout=60) 593:await ctx.send("Proceed?", view=view) 594:await view.wait() 595:
596:
597:---
598:
599:## π Security Checklist
600:
601:| Rule | Why |
602:|:---|:---|
603:| β Never commit DISCORD_TOKEN or webhook URLs | Theyβre bearer credentials |
604:| π Bind HTTP API to 127.0.0.1 only | No external exposure |
605:| β³ Rate-limit webhook calls (5/5s) | Discord enforces this |
606:| π‘οΈ Validate all Discord input | Before passing to OMP tools |
607:| π― Use least-privilege bot permissions | Only what you actually need |
608:| π Store tokens in env vars or systemd Environment= | Not in code or config files |
609:
610:---
611:
612:## πΊοΈ Quick Decision Flowchart
613:
614:text 615:Need Discord integration? 616:β 617:ββ Just notifications? ββββββββββββ πͺ Tier 1 (Webhook) 618:β ββ Drop index.ts, set env var, done. 619:β 620:ββ LLM needs to call Discord? ββββ π§ Tier 2 (MCP) 621:β ββ Write Python MCP server, add mcp.json, done. 622:β 623:ββ Full two-way bridge? βββββββββββ π Tier 3 (Extension + Bot) 624: ββ OMP ext (.ts) + sidecar bot (.py) + systemd service. 625:
626:
627:---
628:
629:## π Reference Links
630:
631:| Resource | URL |
632:|:---|:---|
633:| discord.py Docs | https://discordpy.readthedocs.io/ |
634:| MCP Spec | https://spec.modelcontextprotocol.io/ |
635:| MCP Python SDK | pip install mcp |
636:| OMP Extension Docs | omp://extensions.md |
637:| OMP MCP Config | omp://mcp-config.md |
638:| OMP Plugin System | omp://plugin-manager-installer-plumbing.md |
639:
640:---
641:
642:Last updated: 2026-06-08 Β· Grounded in OMP extension docs, pi-langfuse reference impl, and discord.py v2.x API.
643:
π€ Assistant
π€ Toolresult
Remaining items (5):
- Draft discord-omp-plugin-bp blueprint page [in_progress] (Author)
- Write file to /home/dv/hugo-llm-wiki/content/topics/ [pending] (Author)
- Fix ownership and rebuild Hugo site [pending] (Publish)
- Verify live page returns 200 OK [pending] (Publish)
- Add blueprint link to topics index [pending] (Polish)
Phase 2/4 βAuthorβ β 0/2 tasks complete
Research:
β Read existing cheat sheet content
β Read llm-wiki site config and structure
β Read topics _index.md for nav patterns
Author:
β Draft discord-omp-plugin-bp blueprint page
β Write file to /home/dv/hugo-llm-wiki/content/topics/
Publish:
β Fix ownership and rebuild Hugo site
β Verify live page returns 200 OK
Polish:
β Add blueprint link to topics index
π€ Assistant
π€ Assistant
π€ Assistant
π€ Assistant
π€ User
Embark on an ultra deep yet holistic reflective reasoning, analyzing your last output meticulously and comprehensively yet precisely, listing and brainstorming EVERYTHING that you deem improvable: make it absolute masterfully genius 100% perfect in every way, shape and form! Consider EVERYTHING established so far, the users request(s) and latest best practices, preparing for the final perfect enhanced version. Masterfully proceed as proposed most genius and efficiently while always considering everything established so far.
π€ Assistant
Thinking Process
The user wants me to deeply analyze the existing cheat sheet at
/topics/discord-omp-plugin/and then create a companion blueprint page at/topics/discord-omp-plugin-bp/. The blueprint should be a dense, focused document covering:
- Entire stack
- Architecture
- UI/UX
- Data model
- Routes
- Integration
- Rollout phases
Let me think deeply about what the existing cheat sheet covers and what the blueprint should add:
Existing cheat sheet covers:
- Three-tier architecture overview (Webhook / MCP / Extension+Sidecar)
- Code snippets for each tier
- OMP extension contract
- discord.py essentials (intents, hybrid commands, cogs, background tasks, views)
- Security checklist
- Decision flowchart
- Packaging as distributable plugin
What the blueprint should cover (that the cheat sheet doesnβt):
The blueprint needs to go deeper into the full stack specificationβevery technology, version, runtime, and dependency from discord.py to SQLite for state management to systemd for process control to nginx for reverse proxying. It should include proper system architecture diagrams showing process topology, what runs where, and how data flows between components. Beyond that, I need to detail the data model with all entities and relationships, define every API route and webhook endpoint, specify the UI/UX patterns for the Discord interface, and lay out a phased rollout strategy with migration paths. HTTP API endpoints with full specifications, WebSocket protocol details if needed, MCP tool schemas, and the OMP command registry. Then Iβm mapping out how everything integratesβenvironment variables, service dependencies, health checks, monitoring, logging, and error propagation across the system. Finally, Iβm laying out a phased rollout from basic notifications through to production hardening and advanced features.
Now Iβm thinking about what would really elevate thisβdetailed ASCII architecture diagrams showing process boundaries and data flow, sequence diagrams tracing a Discord command through the entire stack, actual data models with SQL and TypeScript definitions, comprehensive configuration matrices for every environment variable, error handling strategies for each failure point, observability setup with logging and metrics, Discord UX patterns with embed templates and button layouts, the security model with auth flows and token management, and performance considerations around rate limits and caching.
I want to structure this as a dense, blueprint-style document with tables and diagrams, cross-referenced back to the cheat sheet for implementation details, where each section stands alone but connects to the others. The focus should be on what needs to be built and why, leaving the how to the cheat sheet itself.
Now Iβm thinking through the data model more carefully β what tables and interfaces would a production Discord Γ OMP bridge actually need, like sessions to track OMP workflows triggered from Discord, message logs, and other state management.
Iβm also sketching out the HTTP API endpoints for notifications, health checks, channel management, and async task handling, plus the OMP extension state and Discord embed templates for different notification types like agent lifecycle events and task progress.
Now thinking through the UI/UX patterns β Discord side uses color-coded embeds for severity levels, interactive buttons for approvals and retries, dedicated threads per OMP session, modal forms for configuration, select menus for workspace and model selection, autocomplete on slash commands, typing indicators during agent execution, and error messages with clear next steps. On the OMP side, Iβm adding a Discord connection status indicator in the TUI, toast notifications when commands arrive, and slash command integration.
Mapping out the rollout strategy across four phases: starting with project setup and bot configuration, moving into one-way webhook notifications for agent lifecycle events, then adding the MCP tool layer so the LLM can interact with Discord directly, and finally building the full bidirectional bridge with HTTP API, OMP extension hooks, and thread management in Discord.
Then Iβm planning Phase 4 for production hardening with systemd services, health checks, rate limiting, and monitoring, followed by Phase 5 for advanced features like interactive buttons, modals, multi-guild support, and a live stats dashboard.
Before I write the blueprint, I need to ensure the front matter follows the siteβs conventions with YAML delimiters and proper metadata, and I want to make this genuinely useful by cross-referencing the cheat sheet for implementation details, documenting the architectural decisions behind each choice, and calling out anti-patterns to avoid.
Iβm also thinking through what visual aids would help mostβa process topology diagram showing all the connections, a sequence diagram for how Discord commands flow through the system, a state machine for connection states, a data flow visualization, and a deployment topology showing where everything runs.
Now Iβm mapping out the complete project directory structure with the bot entry point, cogs for different command types, an API server layer, and database models.
Iβll deeply analyze the existing cheat sheet, cross-reference with the OMP extension API surface Iβve loaded, and identify every gap that the blueprint must fill. Then Iβll write and publish it.
Analysis of existing cheat sheet vs. blueprint requirements:
| Dimension | Cheat Sheet Has | Blueprint Needs |
|---|---|---|
| Stack | Implicit (discord.py, Bun/TS) | Explicit version matrix, every dep + runtime |
| Architecture | 3 ASCII boxes per tier | Process topology, sequence flows, failure modes, state machines |
| UI/UX | Basic embed + one View example | Full embed templates, button matrices, thread strategy, modal forms, OMP TUI indicators |
| Data Model | None | SQLite schema, Pydantic models, TS interfaces, API payloads |
| Routes | 3 endpoints mentioned | Full REST spec with methods, bodies, responses, status codes |
| Integration | Env vars scattered in snippets | Configuration matrix, service dependency graph, health checks |
| Security | 6-row checklist | Auth flow, permission scoping, token rotation, rate-limit strategy |
| Rollout | None | Phased plan with acceptance criteria per phase |
| Observability | None | Logging strategy, structured log schema, alerts |
| Testing | None | Test strategy per layer |
| Anti-patterns | 1 bundling warning | Decision records with rationale + escape hatches |
| File structure | Flat snippets | Complete project tree |
Now drafting and publishing:
π€ Toolresult
Remaining items (5):
- Draft discord-omp-plugin-bp blueprint page [in_progress] (Author)
- Write file to /home/dv/hugo-llm-wiki/content/topics/ [pending] (Author)
- Fix ownership and rebuild Hugo site [pending] (Publish)
- Verify live page returns 200 OK [pending] (Publish)
- Add blueprint link to topics index [pending] (Polish)
Phase 2/4 βAuthorβ β 0/2 tasks complete
Research:
β Read existing cheat sheet content
β Read llm-wiki site config and structure
β Read topics _index.md for nav patterns
Author:
β Draft discord-omp-plugin-bp blueprint page
β Write file to /home/dv/hugo-llm-wiki/content/topics/
Publish:
β Fix ownership and rebuild Hugo site
β Verify live page returns 200 OK
Polish:
β Add blueprint link to topics index