Skip to content
This repository was archived by the owner on Jul 7, 2026. It is now read-only.
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
59 changes: 59 additions & 0 deletions .agents/skills/refactor/SKILL.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
---
name: refactor
description: >
Use when cleaning or refactoring this Gorkie Slack codebase. Covers the local
cleanup style: simplifying names, collapsing thin helpers, splitting real
ownership boundaries, trimming tool schemas, preserving Slack/AI behavior, and
running the repo validation suite.
---

# Gorkie Refactor

Clean by reducing jumps, not by adding architecture.

## Pass Shape

1. Read the nearby source first. Do not rename or split from vibes.
2. Find names that describe implementation history instead of current purpose.
Prefer direct names like `reply`, `turns`, `buildPrompt`, `annotateMentions`.
3. Collapse helpers that only wrap one line or have no ownership. Inline them at
the call site unless they hide a real boundary.
4. Split files only when a module owns a coherent concept: turn state, compaction,
sandbox setup, Slack mention annotation, tool factories, task rendering.
5. Make factory naming consistent. If most factories use `*Tool`, fix outliers
without changing model-facing tool keys.
6. Keep schemas terse. Add `.describe()` only for fields whose contract is not
obvious from the name.
7. Keep type ownership explicit. Private one-file shapes stay inline; shared or
exported shapes live in the nearest clear owner `types/` folder. App-wide bot
shapes can live under `apps/bot/src/types/`. Tool-owned shared shapes use a
`types/tools/<tool>.ts` path.
8. Remove stale docs and prompts after renames. Search for old names before
handoff.

## Gorkie-Specific Smells

- `index.ts` files that own lifecycle, state, IO, and helpers at once.
- `create*`, `build*`, `with*`, `resolve*` names that hide a tiny operation.
- Three optional fields where a discriminated shape is clearer.
- Long async closures inside tool factories or object literals.
- User-facing task names that describe internal status instead of the action.
- Exported interfaces living in implementation files when an owned `types/`
folder is the clearer home.
- Tool-owned shared types outside a `types/tools/` folder.
- Stale prompt/docs references after tool or agent module renames.

## Validation

After code changes, run:

```bash
bun run fix
bun run typecheck
bun run check
bun run check:spelling
bun run check:knip
```

For file moves, deleted exports, or public entry points, also run stale-name
searches with `rg` and, when practical, a non-starting import smoke.
1 change: 1 addition & 0 deletions .claude/skills/refactor
6 changes: 3 additions & 3 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,8 +19,8 @@ Each conversation gets its own persistent E2B sandbox. The sandbox is remote Lin
- Chat SDK: `https://github.com/vercel/chat`
- Pi internals when needed: `https://github.com/earendil-works/pi`
- Docs and architecture: start in `docs/index.md`.
- Cleanup tracker: `REWRITE_TODO.md`.
- Long-form build plan and coding examples: `REWRITE_PLAN.md`.
- Cleanup tracker: `TODO.md`.
- Long-form build plan and coding examples: `plans/rewrite.md`.

## Where Things Belong

Expand All @@ -42,7 +42,7 @@ Each conversation gets its own persistent E2B sandbox. The sandbox is remote Lin

## Coding Rules

Full detail and examples live in `REWRITE_PLAN.md`.
Full detail and examples live in `plans/rewrite.md`.

- Inline over extract: no one-shot helpers.
- Avoid constants unless absolutely needed: inline one-use literals and values.
Expand Down
181 changes: 0 additions & 181 deletions REWRITE_TODO.md

This file was deleted.

290 changes: 290 additions & 0 deletions TODO.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,290 @@
# Gorkie TODO

Concrete remaining work for the current rewrite, plus open TODOs imported from
the v1 reference codebase. The active v2 tracker comes first. The rewrite plan
lives in `plans/rewrite.md`.

## Slack Context And History

### Summaries

- [ ] Revisit `summarizeThread` subagent design after there is an ephemeral
Harness/Pi session path that does not create persistent sandbox rows.

### Context Prelude

- [ ] Add a bounded Slack context prelude before each Pi prompt so Gorkie starts
with the local Slack situation instead of discovering basic context through
tools.
- [ ] Thread mention preload: when mentioned in a Slack thread, fetch the latest
thread messages before prompting Pi. Target the last 50 thread messages,
sorted chronologically.
- [ ] Channel root mention preload: when mentioned in a channel root message,
fetch recent top-level channel messages before prompting Pi. Target the last
20 channel messages, sorted chronologically, excluding irrelevant bot/self
noise.
- [ ] Make prompt text explicit about which context was preloaded, the bounds
used, and that anything outside those bounds requires Slack/Chat tools.
- [ ] Bound by message count first, then add a token/character budget if real
Slack threads produce oversized prompts. Trim oldest messages first while
preserving the triggering message and direct parent/root.
- [ ] Include attachments only through Chat SDK supported paths. `toAiMessages`
can include images and text-like files when `fetchData()` exists; log skipped
unsupported attachments without failing the turn.

### Chat SDK Reads

- [ ] Use Chat SDK APIs first: adapter `fetchMessages` for thread replies and
adapter `fetchChannelMessages` for channel context. Use Chat SDK state helpers
only when pagination semantics are explicitly needed.
- [ ] Convert fetched Slack messages with `toAiMessages` from `chat/ai` when
model-message shaped context is useful. Prefer `includeNames: true` for
multi-user thread context so Pi can distinguish speakers.
- [ ] Keep the model-facing history surface small: `listThreads` for public
channel thread discovery and `readConversationHistory` for public
channel/thread reads. Add another reader only if a real workflow cannot be
completed cleanly.
- [ ] Fix private-channel/thread access behavior. Gorkie should not let one user
use bot/tool access as global private-channel memory.

### User-Scoped Search

- [ ] Add per-user Slack search using the Acting User's user token, not a shared
bot/admin token, so Slack filters public channels, private channels, DMs,
group DMs, users, and files to exactly what that user can access.
- [ ] Store Slack user-token authorization separately from bot credentials.
Token lookup must be keyed by Acting User, and tool calls must fail closed
when the requesting user has not authorized the needed user scopes.
- [ ] Add user-token search tools for Slack content, files, and users with
explicit result provenance. Tool results should say they came from the Acting
User's Slack-visible scope, not from global bot memory.
- [ ] Keep user-token search privacy scoped. Do not persist raw private-channel
or DM search results into shared agent memory, global caches, or another
user's thread context.
- [ ] Add shared-channel guardrails. If a user searches private DMs or private
channels from a public or multi-user thread, require a private
draft/confirmation step before posting sensitive results back to the shared
thread.

## Slack Actions And UX

### Thread Behavior

- [ ] Verify Slack root mention, reply-only mention, and subscribed-thread
behavior.
- [ ] Fix dev channel enforcement for the `#gorkie` channel.

### Posting And Drafting

- [ ] Support user-token DM actions only behind per-user authorization. Starting
DMs/group DMs and sending messages on behalf of the Acting User must never
silently fall back to a shared user token.
- [ ] Add DM draft lifecycle task renderers: drafting, awaiting approval,
edited, sent, canceled, and failed. Do not dump private message bodies into
logs or task summaries.
- [ ] Evaluate Slack carousel blocks for multi-item tool outputs, especially
generated images and file lists. Decide whether carousel output should replace
repeated file posts after task rendering is stable.
- [ ] Add assistant-sidebar context features, such as visible channel id/context
metadata. Reference commit: `b3da0360c12bb8d1c28fd9849c18fbb747845698`.

## Streaming And Task Rendering

- [ ] Decide whether `apps/bot/src/lib/ai/stream/index.ts` should keep its three
stream-state collections or move to a small state object. Refactor only if it
clearly reduces clutter.
- [ ] Live verify markdown-heavy long Slack responses split into follow-up
messages without `msg_too_long`, broken tables, broken code fences, stranded
list items, or dangling intro lines.
- [ ] Verify task activity stays under Slack limits when reasoning blocks and
tool rows are both present. Slack becomes unstable around large task counts.
- [ ] Refactor error message rendering so user-facing failures are lower-case,
stage-aware, and do not confuse provider failures with Slack rendering bugs.

## Sandbox And Commands

### Sandbox Recovery

- [ ] Verify sandbox deletion recovery: delete or destroy a stored sandbox, send
a follow-up in the same Slack thread, confirm v2 creates a fresh sandbox,
re-seeds the mirrored Pi session file, and preserves conversation memory.

### Command Execution Hangs

- [ ] Add a prompt rule for background commands: redirect output and detach, for
example `python -m http.server 8080 > /tmp/server.log 2>&1 &`.
- [ ] Investigate a host-owned bash override or sandbox `run` wrapper that
starts commands in a process group and kills the group on abort/timeout. This
is the real fix for orphaned background processes that keep stdout/stderr
pipes open after the shell exits.

### Provider Timeout Evidence

- [ ] Handle retryable provider 524s cleanly. Observed failure:
`qwen3.7-max` via `opencode-go` returned a Cloudflare 524
`origin_response_timeout` from `dashscope-us.aliyuncs.com` with
`retryable: true` and `retry_after: 120`.

## Models, BYOK, And Providers

### BYOK Implementation

- [ ] Add `BYOK_ENCRYPTION_KEY` to bot env validation and deployment docs.
Require enough entropy for AES-256-GCM key derivation.
- [ ] Add a bot-owned versioned secret encryption helper.
- [ ] Add tests for the encryption helper: round-trip, tamper failure,
wrong-key failure, malformed ciphertext, and no plaintext in thrown errors.
- [ ] Add `user_model_credentials` Drizzle schema and queries keyed by
`(user_id, provider)`. Store encrypted key, base URL metadata, provider slug,
selected model id, key preview, validation status, validation message,
last-used timestamp, and audit timestamps.
- [ ] Keep raw BYOK secrets out of `packages/db` query logs and return types
unless a bot-owned decrypting service explicitly asks for the encrypted
payload.
- [ ] Define a single exported provider discriminant union and provider adapter
map in `packages/ai` for Pi attempts: OpenRouter-compatible, opencode-go, and
any direct provider added later.
- [ ] Replace static `chatAttempts` call sites with per-Acting-User attempt
resolution: BYOK attempts first, service attempts only when no BYOK exists or
explicit service fallback is enabled.
- [ ] Update turn execution, compaction, attempt selection, and attempt logging
so provider/model selection is per turn and logs never include raw env or key
material.
- [ ] Make `generateImage` policy explicit in code/UI: service image provider
only unless an image-capable BYOK provider is configured.
- [ ] Add App Home Model Keys UI: add/rotate/delete credential, provider input,
model id input, optional base URL, status display, and masked key preview
only.
- [ ] Ensure Slack modal `private_metadata`, view state re-renders, prompt
hints, Pi session files, E2B env, task renderers, and logs never contain raw
API keys.
- [ ] Validate credentials on save or first use, store safe validation status,
and surface invalid-key/auth/quota errors only to the Acting User where Slack
allows.
- [ ] Decide service fallback UX: default off for BYOK failures, with explicit
opt-in if users may spend the shared service key after their key fails.
- [ ] Add tests for provider-to-`customEnv` mapping, BYOK-first fallback order,
service fallback disabled, invalid-key handling, App Home modal parsing, and
ownership checks.
- [ ] Live smoke: save a key, run a Slack turn, run compaction, rotate the key,
delete the key, trigger an invalid-key turn, and verify two users in one
thread use separate credentials without leaking provider/key details.

### Provider Selection

- [ ] Investigate why Gorkie sometimes does not switch models properly.
- [ ] Verify Langfuse receives AI SDK spans using `@ai-sdk/otel` plus
`LangfuseSpanProcessor`. Current note: it does not.

## Verification Matrix

- [ ] Add tests or harnessed smoke coverage for thread mention with 50-message
cap, channel mention with 20-message cap, DM follow-up, failed history fetch,
and attachment-skipping behavior.
- [ ] Live verify in Slack that Gorkie can answer a thread-context question from
the preload and can still call `readConversationHistory` for older or broader
context.

## Upstream AI SDK And Harness Bets

- [ ] Native MCP support and skill support.
- [ ] Native steering support exposing queued user messages or
`submitUserMessage` cleanly.
- [ ] Refactored Pi provider selection that is not ENV-prefix and
model-id-order dependent.
- [ ] `ai-retry` support at the Harness/Pi boundary so custom retry logic can be
deleted.
- [ ] Native Langfuse/OTel support deep enough for Harness/Pi model, tool, and
session internals.
- [ ] Official AI SDK E2B provider support with the resume/session-file hooks
Gorkie needs.
- [ ] Pi-level retry parity with the old implementation so transient provider
failures can retry inside Pi before Gorkie's outer attempt fallback runs.

## V1 Reference TODOs

Imported from `/workspaces/worktrees/gorkie-slack/reference/TODO.md`. These are
not automatically v2 requirements, but they capture bugs and product gaps found
in the old implementation.

### Provider And Model Reliability

- [ ] Add a configurable output cap for chat and sandbox model calls so provider
fallback requests do not reserve unaffordable default output budgets. The v1
trace showed HackClub returning Cloudflare 504 HTML, then OpenRouter rejecting
fallback with a 402 because it reserved up to `65_536` output tokens.
- [ ] Add multi-provider retry for the Pi sandbox agent so coding-agent failures
can fall back to alternative providers instead of ending the task.
- [ ] Evaluate whether the primary chat model should move from Gemini Flash to
a smaller GPT-5 family model. Document cost, latency, context, and quality
tradeoffs before changing defaults.
- [ ] Remove the local `ai-retry` patch when upstream supports AI SDK 7 without
local declaration changes. Until then, keep AI SDK packages on the same v7
canary line, keep `ai-retry` pinned, and smoke-test provider fallback after
provider or AI SDK bumps.

### MCP And OAuth

- [ ] Show manually denied tools in the thinking/task panel after approval
resume. Denied approvals need synthetic pre-tasks in the new response stream.
- [ ] Improve MCP tool task display. Show the normalized MCP tool name in the
title, such as `Using Fathom: list_meetings`, plus concise clamped input and
output previews.
- [ ] Lock down connection-defining MCP server fields after creation. URL,
transport, and auth type should require deleting and re-adding the server.
- [ ] Add scheduled MCP OAuth token refresh before expiry and record refresh
failures without breaking unrelated MCP servers.
- [ ] Add MCP tool discovery before full OAuth so users can preview available
tools in the Slack setup flow.
- [ ] When setting an entire MCP tool group, apply the change to the whole group,
not only the currently visible pagination page. Search results can stay scoped
to the visible results.

### Slack Stream And Task UX

- [ ] If a terminal tool fires without visible reasoning, show the terminal task
directly, such as `Replied`, `Skipping`, or `Left channel`, instead of a
generic thinking task.
- [ ] Investigate intermittent missing thinking/reasoning in Slack task streams.
Verify reasoning lifecycle events appear before tool calls and remain visible
after terminal tasks.
- [ ] Prevent Slack task cards from overflowing block limits. Start a
continuation task card when the current card approaches Slack limits.
- [ ] Make Slack search errors non-fatal so a search failure does not mark an
otherwise successful task as failed.
- [ ] Fix pinned-item detection for Slack search results. Confirm whether
user-specific pinning requires `pins.list`, `conversations.info`, or another
Slack API path.
- [ ] After sandbox upload tools run, inject the uploaded file list into the
model-visible response context so the agent knows what was uploaded.

### Sandbox And Browser Tooling

- [ ] Investigate why `agent-browser --snapshot /path/to/file.png` does not save
files where the sandbox agent expects them. Determine whether it writes
relative to the command CWD, daemon CWD, or a temp directory.
- [ ] Add `agentmail` to the E2B sandbox template or document it as a first-run
install for the `agent-browser` skill.

### Cleanup And Package Shape

- [ ] Clean up message pipeline utilities from the old Slack stack:
`message-context.ts`, `triggers.ts`, `conversations.ts`, and `context.ts`.
Remove duplicated `users.info` patterns and tighten stale types if any of
that code is reintroduced.
- [ ] Continue reducing schema and type clutter. Move reusable types into clear
type owners, keep Slack actions/views feature-owned, and avoid one-shot
helpers.
- [ ] Consider whether more package extraction is worth it, especially
observability and sandbox logic, if those areas become shared by bot and
server code.
- [ ] Build out `packages/kv` before any feature uses it. Add typed key builders
and query helpers, and decide whether serverless paths need Upstash instead
of a persistent Redis connection.

### Workflow

- [ ] Add an opt-in checkpoint flow for larger agent tasks. Commits must stay
scoped to the current task and avoid unrelated dirty worktree changes.
- [ ] If a planning thread exceeds roughly 50 messages, start a new planning
block to avoid context degradation.
Loading
Loading