{"id":50750444,"url":"https://github.com/dcai/discord-pi-agent","last_synced_at":"2026-06-11T01:01:30.020Z","repository":{"id":353476732,"uuid":"1218075954","full_name":"dcai/discord-pi-agent","owner":"dcai","description":null,"archived":false,"fork":false,"pushed_at":"2026-06-10T10:16:49.000Z","size":1633,"stargazers_count":0,"open_issues_count":0,"forks_count":0,"subscribers_count":0,"default_branch":"main","last_synced_at":"2026-06-10T10:23:34.002Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":null,"language":"TypeScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/dcai.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":"AGENTS.md","dco":null,"cla":null}},"created_at":"2026-04-22T14:04:33.000Z","updated_at":"2026-06-10T10:16:53.000Z","dependencies_parsed_at":null,"dependency_job_id":null,"html_url":"https://github.com/dcai/discord-pi-agent","commit_stats":null,"previous_names":["dcai/discord-pi-agent"],"tags_count":129,"template":false,"template_full_name":null,"purl":"pkg:github/dcai/discord-pi-agent","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/dcai%2Fdiscord-pi-agent","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/dcai%2Fdiscord-pi-agent/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/dcai%2Fdiscord-pi-agent/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/dcai%2Fdiscord-pi-agent/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/dcai","download_url":"https://codeload.github.com/dcai/discord-pi-agent/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/dcai%2Fdiscord-pi-agent/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":34177444,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-05-26T15:22:16.424Z","status":"online","status_checked_at":"2026-06-10T02:00:07.152Z","response_time":89,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":[],"created_at":"2026-06-11T01:00:37.104Z","updated_at":"2026-06-11T01:01:29.955Z","avatar_url":"https://github.com/dcai.png","language":"TypeScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"# @friendlyrobot/discord-pi-agent\n\nReusable Discord gateway for persistent pi agent sessions — DM and forum channels.\n\n## What it does\n\n- runs persistent pi agent sessions (one per DM, one per forum thread)\n- resumes sessions on restart via scoped session directories\n- loads project context from the target repo via pi resource loading\n- accepts DM messages and forum thread messages from allowed users\n- serializes prompts per-scope through FIFO queues\n- exposes built-in session commands (per-scope, including `!archive`)\n- can run scheduled prompt jobs from a JS/TS jobs file\n- can run in Discord-only, scheduler-only, or combined mode\n\n## Built-in commands\n\n- `!help`\n- `!status`\n- `!thinking`\n- `!model`\n- `!compact`\n- `!reload`\n- `!remind \u003cwhen\u003e, \u003ctask\u003e`\n- `!jobs`\n- `!job \u003cid\u003e`\n- `!job run \u003cid\u003e`\n- `!job run-here \u003cid\u003e`\n- `!job update \u003cfreeform request\u003e`\n- `!jobs reload`\n- `!reset-session`\n- `!archive` (forum threads only — archives the thread and shuts down the session)\n\nAny other text is sent to the active session (DM or thread).\n\nWhen the scheduler is enabled, `!jobs` shows the loaded runtime state with a prompt preview for each job, `!job \u003cid\u003e` shows one job with its full prompt, `!job run \u003cid\u003e` runs a loaded job immediately with its configured result target, `!job run-here \u003cid\u003e` runs a loaded job immediately but overrides delivery to the current DM or thread, `!jobs reload` reloads the jobs file without restarting the process, and `!job update \u003cfreeform request\u003e` turns your request into a scheduler-aware agent prompt that edits the jobs file in the normal agentic way.\n\n`!remind \u003cwhen\u003e, \u003ctask\u003e` creates a one-off runtime reminder from natural language. It is parsed through a temporary in-memory agent session, shows up in `!jobs`, runs once, and is then forgotten. It is not written back to the scheduled jobs file. Runtime reminders always target the current Discord conversation by saving `message.channel.id` as a `discord-channel` result target. In a DM, that is the DM channel ID. In a forum thread, that is the thread ID.\n\n## Prompt metadata\n\nEvery Discord prompt is wrapped with lightweight Discord context before `promptTransform` runs:\n\n```text\n\u003cdiscord_message_context\u003e\n{\n  \"scope\": \"thread\",\n  \"sent_at\": \"2026-05-07T04:31:00.000Z\",\n  \"sent_at_local\": \"Thu, 7 May 26, 14:31 AEST\",\n  \"message_id\": \"...\",\n  \"author_name\": \"Alice\",\n  \"author_id\": \"...\",\n  \"thread_title\": \"Bug report\",\n  \"thread_id\": \"...\",\n  \"forum_channel_id\": \"...\"\n}\n\u003c/discord_message_context\u003e\n\n\u003cuser_message\u003e\n...\n\u003c/user_message\u003e\n```\n\nDM prompts omit thread-only fields. `sent_at_local` uses `promptTimeZone` and `promptLocale`.\n\n## Install\n\n```bash\nnpm install @friendlyrobot/discord-pi-agent\n```\n\n## Usage\n\n```ts\nimport {\n  loadDiscordGatewayConfigFromEnv,\n  startDiscordGateway,\n} from \"@friendlyrobot/discord-pi-agent\";\n\nconst config = loadDiscordGatewayConfigFromEnv({\n  cwd: process.cwd(),\n  promptTimeZone: \"Australia/Sydney\",\n  promptLocale: \"en-AU\",\n  // Enable forum channel support (omit for DM-only)\n  discordAllowedForumChannelIds: [\"1498563501780897832\"],\n});\n\nawait startDiscordGateway(config);\n```\n\nEach forum post creates a scoped pi session in `sessions/thread-\u003cid\u003e/`.\nThe initial post body becomes the first prompt. Sessions survive restarts.\n\n### Discord + scheduler\n\n```ts\nimport {\n  loadDiscordGatewayConfigFromEnv,\n  startDiscordGateway,\n} from \"@friendlyrobot/discord-pi-agent\";\n\nconst config = loadDiscordGatewayConfigFromEnv({\n  cwd: process.cwd(),\n});\n\nawait startDiscordGateway(config, {\n  scheduler: {\n    jobsFile: \"./scheduled-jobs.ts\",\n  },\n});\n```\n\n### Scheduler-only mode\n\n```ts\nimport {\n  loadDiscordGatewayConfigFromEnv,\n  startTaskScheduler,\n} from \"@friendlyrobot/discord-pi-agent\";\n\nconst config = loadDiscordGatewayConfigFromEnv({\n  cwd: process.cwd(),\n});\n\nawait startTaskScheduler(config, {\n  jobsFile: \"./scheduled-jobs.ts\",\n});\n```\n\nScheduler-only mode does not handle inbound Discord user messages. It only runs scheduled jobs and sends results to the configured targets.\n\n## Scheduled jobs\n\nScheduled jobs are defined in a trusted JS/TS module. The file can export either:\n\n- `jobs`\n- `defineJobs()`\n- a default export with either of those shapes\n\nExample:\n\n```ts\nimport {\n  defineScheduledJobs,\n  type ScheduledTaskDefinition,\n} from \"@friendlyrobot/discord-pi-agent\";\n\nexport const jobs: ScheduledTaskDefinition[] = defineScheduledJobs([\n  {\n    id: \"repo-heartbeat\",\n    schedule: {\n      type: \"every-minutes\",\n      interval: 30,\n    },\n    prompt: \"Check the repo and summarize anything important.\",\n    result: {\n      target: \"logs\",\n    },\n  },\n  {\n    id: \"daily-standup\",\n    schedule: {\n      type: \"daily-at\",\n      hour: 9,\n      minute: 0,\n      timeZone: \"Australia/Sydney\",\n    },\n    prompt: \"Review recent work and draft a standup update.\",\n    session: {\n      strategy: \"reuse\",\n      scope: \"dm\",\n    },\n    result: {\n      target: \"discord-dm\",\n      userId: \"123456789012345678\",\n    },\n  },\n  {\n    id: \"one-shot-report\",\n    schedule: {\n      type: \"every-minutes\",\n      interval: 60,\n    },\n    prompt: \"Build a quick status report and post it.\",\n    session: {\n      strategy: \"ephemeral\",\n    },\n    result: {\n      target: \"logs\",\n    },\n  },\n]);\n```\n\n`defineScheduledJobs(...)` is optional but recommended. It makes the jobs file contract explicit and validates definitions before the loader consumes them.\n\n### Supported schedules\n\n- `every-minutes`\n- `daily-at`\n\n### Session strategy\n\nWhen `session` is omitted, the default is a fresh dedicated persistent session stored under `sessions/job-\u003cid\u003e/`.\n\n- `strategy: \"fresh\"` — create a fresh persistent session for the run\n- `strategy: \"reuse\"` — reuse the existing persistent session for the job or scope\n- `strategy: \"ephemeral\"` — use a temporary in-memory session for the run\n- `scope: \"dm\" | \"thread:\u003cid\u003e\" | \"job:\u003cid\u003e\"` — optional persistent scope to target with `fresh` or `reuse`\n\nExamples:\n\n- `{ strategy: \"fresh\" }` — fresh dedicated persistent job session\n- `{ strategy: \"reuse\" }` — reuse the dedicated job session at `sessions/job-\u003cid\u003e/`\n- `{ strategy: \"reuse\", scope: \"dm\" }` — reuse the DM session\n- `{ strategy: \"fresh\", scope: \"thread:123\" }` — replace the thread session with a fresh persistent one\n- `{ strategy: \"ephemeral\" }` — one-shot in-memory session, never saved\n\n### Result targets\n\n- `logs`\n- `discord-dm`\n- `discord-channel` — can also target a thread by using the thread ID\n\nDiscord scheduled job deliveries intentionally send each message chunk with embeds suppressed. This keeps digest-style jobs clean when the model includes links. If you still want clickable links without previews, format them as `\u003chttps://example.com\u003e` in the prompt or model output.\n\n## Config\n\n### Required\n\n- `discordBotToken`\n- `discordAllowedUserId`\n- `cwd`\n\n### Optional\n\n- `agentDir` default: `\u003ccwd\u003e/.pi-agent`\n- `modelProvider` default: `openrouter`\n- `modelId` default: `anthropic/claude-3.5-haiku`\n- `thinkingLevel` default: `medium` (values: `off`, `minimal`, `low`, `medium`, `high`, `xhigh`)\n- `promptTimeZone` default: `UTC` — used for `sent_at_local` in Discord prompt metadata\n- `promptLocale` default: `en-AU` — used for `sent_at_local` in Discord prompt metadata\n- `promptTransform` default: identity\n- `startupMessage` default: `Bot is online and ready.`\n- `shutdownOnSignals` default: `true`\n- scheduler via `startDiscordGateway(config, { scheduler: { jobsFile } })`\n\n### Scheduler config\n\n- `jobsFile` — required JS/TS jobs module path, resolved from `cwd` when relative\n\n### Logging\n\nThe package uses `pino` for structured logs.\n\nBehavior:\n\n- when stdout is a TTY, logs use `pino-pretty` for readable local console output\n- when stdout is not a TTY, logs stay as JSON\n\nLog level env vars:\n\n- `DISCORD_PI_AGENT_LOG_LEVEL`\n- `LOG_LEVEL` fallback\n\nDefault level is `info`.\n\nFor detailed prompt and tool monitoring during local runs, use:\n\n```bash\nDISCORD_PI_AGENT_LOG_LEVEL=debug\n```\n\nPretty console logs use:\n\n- colors\n- local timestamp (`SYS:standard`)\n- level first\n- hidden `pid` and `hostname`\n- module-aware labels like `[discord-gateway]`\n- direction markers like `IN` and `OUT`\n- multi-line payload blocks for easier input/output inspection\n\n### Forum channel options\n\n- `discordAllowedForumChannelIds` — string array of forum channel IDs to respond in\n- `discordAllowedUserIds` — string array of allowed user IDs (defaults to `[discordAllowedUserId]`)\n\n## Env helpers\n\n`loadDiscordGatewayConfigFromEnv()` — the config loader:\n\n- `DISCORD_BOT_TOKEN`\n- `DISCORD_ALLOWED_USER_ID`\n- `PI_AGENT_CWD`\n- `PI_AGENT_DIR`\n- `PI_MODEL_PROVIDER`\n- `PI_MODEL_ID`\n- `PI_PROMPT_TIME_ZONE`\n- `PI_PROMPT_LOCALE`\n- `DISCORD_STARTUP_MESSAGE`\n- `DISCORD_FORUM_CHANNEL_IDS` — comma-separated forum channel IDs\n- `DISCORD_ALLOWED_USER_IDS` — comma-separated allowed user IDs\n\nIf `PI_AGENT_CWD` is missing it falls back to `process.cwd()`.\nSet `DISCORD_STARTUP_MESSAGE=false` to disable the startup DM.\n\n## Thinking Levels\n\nUse `!thinking` to view the current thinking/reasoning level and available options. Use `!thinking \u003clevel\u003e` to set it (e.g., `!thinking high`).\n\nNot all models support thinking/reasoning. The configured `thinkingLevel` is applied automatically when the model supports it.\n\n## Build\n\n```bash\nnpm run build\nnpm run typecheck\n```\n\n## Dependency updates\n\nTo check for newer package versions and update `package.json`, run:\n\n```bash\nnpx npm-check-updates -u\nnpm install\n```\n\nThis is the npm-side replacement for the old `bun update` workflow.\n\n## Notes\n\n- DM and forum threads supported via `startDiscordGateway`\n- scheduled jobs are opt-in through `startDiscordGateway(config, { scheduler })`\n- `startTaskScheduler()` runs the scheduler without inbound Discord message handling\n- Forum thread sessions are stored in `sessions/thread-\u003cid\u003e/` (one directory per thread)\n- Scheduled job sessions are stored in `sessions/job-\u003cid\u003e/` when using dedicated persistent sessions\n- Ephemeral scheduled jobs use in-memory sessions and do not write session files\n- Sessions survive restarts — `SessionManager.continueRecent()` resumes the latest `.jsonl`\n- Single Discord client with all intents (DM + Guild + MessageContent)\n- No mode flags — forum support activates when `discordAllowedForumChannelIds` is set\n- The package does not register Discord slash commands\n- pi resources are loaded from the configured `cwd` and `agentDir`\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fdcai%2Fdiscord-pi-agent","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fdcai%2Fdiscord-pi-agent","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fdcai%2Fdiscord-pi-agent/lists"}