-
Notifications
You must be signed in to change notification settings - Fork 0
Add pluggable inbound-webhook framework + Granola meeting summarizer #4
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Open
Miyamura80
wants to merge
5
commits into
main
Choose a base branch
from
claude/webhook-listener-meetings-56Ucc
base: main
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Open
Changes from all commits
Commits
Show all changes
5 commits
Select commit
Hold shift + click to select a range
7fca347
Add pluggable inbound-webhook framework with Granola meeting summarizer
claude dc6d91c
Harden webhook input validation and codex exit handling
claude 0bb7814
Document launchd deployment and add LaunchAgent template
36b5fae
Fix deployment template review findings
claude 3ac8890
Fix cross-user note and codex diagnostic accuracy
claude File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,41 @@ | ||
| <?xml version="1.0" encoding="UTF-8"?> | ||
| <!-- | ||
| launchd LaunchAgent template for the Slack bot. See docs/DEPLOY.md. | ||
|
|
||
| This is a TEMPLATE: __USER__ and __WORKDIR__ are placeholders that must be | ||
| substituted before installing to ~/Library/LaunchAgents/. The install command | ||
| in docs/DEPLOY.md renders it with sed. Do NOT add secrets — env comes from | ||
| .env in WorkingDirectory (auto-loaded by Bun). | ||
| --> | ||
| <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd"> | ||
| <plist version="1.0"> | ||
| <dict> | ||
| <key>Label</key><string>com.edison.assistant-bot</string> | ||
|
|
||
| <key>ProgramArguments</key> | ||
| <array> | ||
| <string>/opt/homebrew/bin/bun</string> | ||
| <string>run</string> | ||
| <string>index.ts</string> | ||
| </array> | ||
|
|
||
| <!-- Absolute path to your checkout (real path, not a symlink). --> | ||
| <key>WorkingDirectory</key> | ||
| <string>__WORKDIR__</string> | ||
|
|
||
| <key>RunAtLoad</key><true/> | ||
| <key>KeepAlive</key><true/> | ||
| <key>ThrottleInterval</key><integer>10</integer> | ||
|
|
||
| <key>StandardOutPath</key><string>/Users/__USER__/Library/Logs/assistant-bot.log</string> | ||
| <key>StandardErrorPath</key><string>/Users/__USER__/Library/Logs/assistant-bot.log</string> | ||
|
|
||
| <key>EnvironmentVariables</key> | ||
| <dict> | ||
| <key>PATH</key> | ||
| <string>/opt/homebrew/bin:/opt/homebrew/sbin:/usr/local/bin:/usr/bin:/bin:/usr/sbin:/sbin:/Users/__USER__/.bun/bin</string> | ||
| <key>HOME</key> | ||
| <string>/Users/__USER__</string> | ||
| </dict> | ||
| </dict> | ||
| </plist> | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,102 @@ | ||
| # Deployment (macOS launchd) | ||
|
|
||
| The bot runs as a per-user launchd **LaunchAgent** so it auto-starts on login and | ||
| auto-restarts on crash. Template: [`deploy/com.edison.assistant-bot.plist`](../deploy/com.edison.assistant-bot.plist). | ||
|
|
||
| ## Install | ||
|
|
||
| The template contains two placeholders — `__USER__` and `__WORKDIR__` — that must | ||
| be substituted before installing. `sed` handles both: | ||
|
|
||
| ```sh | ||
| # 1. Render the template into ~/Library/LaunchAgents/, substituting the current | ||
| # user and the absolute path to this checkout. | ||
| sed -e "s|__USER__|$USER|g" -e "s|__WORKDIR__|$(pwd -P)|g" \ | ||
| deploy/com.edison.assistant-bot.plist \ | ||
| > ~/Library/LaunchAgents/com.edison.assistant-bot.plist | ||
|
|
||
| # 2. Make sure Redis is running (used for chat state + inbound-webhook dedup). | ||
| brew services start redis | ||
|
|
||
| # 3. Load it (bootstrap into the GUI session so it starts on login). | ||
| launchctl bootstrap gui/$(id -u) ~/Library/LaunchAgents/com.edison.assistant-bot.plist | ||
|
|
||
| # 4. Verify. | ||
| tail -n 20 ~/Library/Logs/assistant-bot.log # expect "Bot is running on http://localhost:3123" | ||
| lsof -i :3123 # expect a bun process LISTEN | ||
| curl -i -X POST http://localhost:3123/api/webhooks/slack -d '{}' # expect 401 (adapter reachable) | ||
| ``` | ||
|
|
||
| Run the install as the Unix user the bot should run under, from the repo root: | ||
| `$USER`, `$(id -u)`, and `~/Library/…` in the block above all resolve against that | ||
| same user. `pwd -P` gives the real checkout path without symlinks — important | ||
| because a symlinked `WorkingDirectory` may hit the external-volume TCC gotcha | ||
| below. `Label` intentionally stays `com.edison.assistant-bot` (the reverse-DNS | ||
| prefix refers to the org, not the local user). | ||
|
|
||
| Env vars (`SLACK_BOT_TOKEN`, `SLACK_APP_TOKEN`, `REDIS_URL`, `WEBHOOK_SECRET`, | ||
| `GRANOLA_SLACK_CHANNEL_ID`) are auto-loaded by Bun from `.env` in `WorkingDirectory`. | ||
| **Do not put secrets in the plist.** | ||
|
|
||
| ## Managing the agent | ||
|
|
||
| ```sh | ||
| launchctl kickstart -k gui/$(id -u)/com.edison.assistant-bot # restart | ||
| launchctl bootout gui/$(id -u)/com.edison.assistant-bot # stop + unload | ||
| launchctl list | grep assistant-bot # status (col 1 = PID, col 2 = last exit) | ||
| ``` | ||
|
|
||
| ## Gotcha: repo on an external / non-boot volume needs Full Disk Access | ||
|
|
||
| If the checkout lives on an **external or removable volume** (e.g. a USB drive under | ||
| `/Volumes/...`, possibly reached via a `~/Github` symlink), macOS **TCC blocks | ||
| launchd-spawned processes from reading file *contents* there** — metadata/`ls` is | ||
| allowed, but `read()` returns `EPERM` ("Operation not permitted"). Symptoms: the agent | ||
| shows a PID but the log is empty, port 3123 never binds, and `curl` gets connection | ||
| refused. It works from an interactive terminal only because Terminal already holds the | ||
| grant. | ||
|
|
||
| **Fix:** grant **Full Disk Access to the bun binary**: | ||
|
|
||
| 1. System Settings → Privacy & Security → **Full Disk Access** → **+** | ||
| 2. Add `/opt/homebrew/bin/bun` (⌘⇧G to type the path), toggle it **on**. | ||
| 3. Restart the agent: `launchctl kickstart -k gui/$(id -u)/com.edison.assistant-bot` | ||
|
|
||
| The grant is keyed to the binary, so after `brew upgrade bun` (its Cellar path/cdhash | ||
| changes) you may need to re-add it. If the volume is slow to mount at boot, the first | ||
| launch attempt exits and `KeepAlive`+`ThrottleInterval` retry every 10s until the volume | ||
| is readable — the agent self-heals. | ||
|
|
||
| Diagnose a suspected TCC block with a boot-volume probe script that `ls`es then `head`s a | ||
| file on the external volume from a throwaway LaunchAgent: `ls` succeeds, `head` fails with | ||
| `Operation not permitted`. | ||
|
|
||
| ## Gotcha: codex CLI must not be on a revoked-cert build | ||
|
|
||
| The Granola webhook shells out to the `codex` CLI (`lib/codex.ts`). If codex hangs on | ||
| **every** invocation — even `codex --version`, stuck in `_dyld_start` before `main()` — | ||
| its signing certificate has likely been **revoked**. Check: | ||
|
|
||
| ```sh | ||
| # Inspect the SAME codex the bot actually spawns. lib/codex.ts runs bare | ||
| # `codex`, which the LaunchAgent resolves via its fixed PATH — not the | ||
| # interactive shell's PATH (which nvm/nodenv/asdf may shadow). Reproduce that | ||
| # PATH here, then find the arch-specific vendor binary near the resolved codex. | ||
| # BSD-safe; no `readlink -f`; works on arm64 and x86_64. | ||
| BOT_PATH="/opt/homebrew/bin:/opt/homebrew/sbin:/usr/local/bin:/usr/bin:/bin:/usr/sbin:/sbin:$HOME/.bun/bin" | ||
| NPM_BIN="$(PATH="$BOT_PATH" command -v npm)" | ||
| [ -z "$NPM_BIN" ] && { echo "npm not on the bot's PATH"; return 1 2>/dev/null || exit 1; } | ||
| NPM_ROOT="$("$NPM_BIN" root -g)" | ||
| CODEX_BIN="$(find "$NPM_ROOT" -type f -name codex -path '*/vendor/*/bin/codex' 2>/dev/null | head -n 1)" | ||
| if [ -z "$CODEX_BIN" ]; then | ||
| echo "codex arch binary not found under $NPM_ROOT" | ||
| else | ||
| spctl --assess -vvv --type execute "$CODEX_BIN" | ||
| fi | ||
| # CSSMERR_TP_CERT_REVOKED => revoked | ||
| ``` | ||
|
|
||
| **Fix:** upgrade to a build signed with a valid cert: `npm install -g @openai/codex@latest`. | ||
| (Observed: 0.129.0 was revoked and hung; 0.144.0 assesses clean and works.) Note `codex | ||
| exec` also requires running from a git repo or trusted dir — the bot satisfies this because | ||
| its `WorkingDirectory` is this repo. |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,53 @@ | ||
| export async function* streamCodex(prompt: string): AsyncIterable<string> { | ||
| const proc = Bun.spawn( | ||
| ["codex", "exec", "--ephemeral", "-s", "read-only", "--json", "-"], | ||
| { stdin: "pipe", stdout: "pipe", stderr: "ignore" }, | ||
| ); | ||
| proc.stdin.write(prompt); | ||
| proc.stdin.end(); | ||
|
|
||
| const decoder = new TextDecoder(); | ||
| let buffer = ""; | ||
| let messageCount = 0; | ||
|
|
||
| for await (const chunk of proc.stdout) { | ||
| buffer += decoder.decode(chunk, { stream: true }); | ||
| const lines = buffer.split("\n"); | ||
| buffer = lines.pop() ?? ""; | ||
|
|
||
| for (const line of lines) { | ||
| if (!line.trim()) continue; | ||
| try { | ||
| const event = JSON.parse(line); | ||
| if ( | ||
| event.type === "item.completed" && | ||
| event.item?.type === "agent_message" && | ||
| event.item.text | ||
| ) { | ||
| if (messageCount > 0) yield "\n\n---\n\n"; | ||
| yield event.item.text; | ||
| messageCount++; | ||
| } | ||
| } catch {} | ||
| } | ||
| } | ||
|
|
||
| if (buffer.trim()) { | ||
|
cubic-dev-ai[bot] marked this conversation as resolved.
|
||
| try { | ||
| const event = JSON.parse(buffer); | ||
| if ( | ||
| event.type === "item.completed" && | ||
| event.item?.type === "agent_message" && | ||
| event.item.text | ||
| ) { | ||
| if (messageCount > 0) yield "\n\n---\n\n"; | ||
| yield event.item.text; | ||
| } | ||
| } catch {} | ||
| } | ||
|
|
||
| const exitCode = await proc.exited; | ||
| if (exitCode !== 0) { | ||
| throw new Error(`codex exited with code ${exitCode}`); | ||
| } | ||
| } | ||
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.