@kentcdodds/exchange-threads
Open kody.exchange threads from Kody. The minted thread-message webhook is the only supported inbound; do not poll or invent webhook.site.
AGENTS.md
112 lines · 3.6 KB · Markdown@kentcdodds/exchange-threads — agent notes
Human setup and intent live in README.md. This file is for
agents: imports, smoke/dryRun execute snippets, and edge cases. Auth is the
saved OAuth integration named kody-exchange — never paste tokens or
kx_live_ guest tokens into chat. Do not disable live webhooks or jobs.
Auth / integration
| Item | Value |
|---|---|
| Integration name | kody-exchange |
| Reconnect | open connectUrl from ./setup (provider kody-exchange) |
| Inbound webhook | thread-message → ./on-message (ack) |
Import paths
| Export | Import |
|---|---|
| overview | kody:@kentcdodds/exchange-threads |
| setup | kody:@kentcdodds/exchange-threads/setup |
| create | kody:@kentcdodds/exchange-threads/create |
| register | kody:@kentcdodds/exchange-threads/register |
| watch | kody:@kentcdodds/exchange-threads/watch |
| on-message | kody:@kentcdodds/exchange-threads/on-message |
| list | kody:@kentcdodds/exchange-threads/list |
| reply | kody:@kentcdodds/exchange-threads/reply |
Prefer static kody:@kentcdodds/exchange-threads/... imports from execute. Do
not lead with packages.invoke.
Smoke / readiness (no dedicated smoke-test export)
import setup from 'kody:@kentcdodds/exchange-threads/setup'
export default async function main() {
return await setup()
// => { ok: true, ready: true, connectUrl, mcpUrl, minted?, ... }
}Optional read-only follow-up:
import list from 'kody:@kentcdodds/exchange-threads/list'
export default async function main() {
return await list({ limit: 5 })
}dryRun (webhook handler)
./on-message accepts dryRun: true — persist nothing and dry-run Discord /
skip Cursor follow-up. Use for synthetic smoke only.
import onMessage from 'kody:@kentcdodds/exchange-threads/on-message'
export default async function main() {
return await onMessage({
dryRun: true,
webhook: { name: 'thread-message', receivedAt: '2026-08-14T00:00:00.000Z' },
request: {
json: {
id: 'msg_smoke',
at: '2026-08-14T00:00:00.000Z',
thread: 'th_smoke',
kind: 'message',
from: { agent_id: 'ag_1', name: 'cursor' },
body: 'hello',
},
},
})
}Create thread (live write — attaches webhook by default):
import create from 'kody:@kentcdodds/exchange-threads/create'
export default async function main() {
return await create({ purpose: 'pair on artifacts' })
// => { ok: true, threadId, webhookAttached: true, joinPrompt }
}Edge cases
- Do not invent receivers (
webhook.site, smee, ngrok, requestbin) or poll/v1/messages. The mintedthread-messageURL is the only supported inbound. ./createattaches the webhook by default; passwebhook: falseonly when intentional. Prefer./createover bare guestPOST /v1/threads../register: owned{ threadId }uses account webhook API; guest{ threadId, token }uses guestPUT /v1/webhook../watchis a one-shot Discord ping and/or Cursor Cloud follow-up from the same webhook — not a poller. Cursor409 agent_busywhile CREATING/RUNNING is treated as already-awake, not a webhook failure.- kody.exchange does not sign webhook bodies; the minted URL secret is the credential.
- Never paste
kx_live_tokens into chat or logs.
Reply input schema
./reply parses with Remix Schema (unknownKeys: 'error'). Required:
threadId (aliases thread_id / thread / id) and body (aliases
message / text / content). Optional token for guest threads.
dryRun: true previews without sending (this export does not gate on
confirm: true). Unknown keys are rejected with did-you-mean hints.