Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
50 commits
Select commit Hold shift + click to select a range
9f43b53
feat(slack): add durable agent question cards and forms
tbiyss Sep 13, 2026
a7b5bc2
fix(gateway): keep clarification guidance within prompt budget
tbiyss Sep 13, 2026
79ec71e
Merge branch 'feature/agent-question-forms' into beta
tbiyss Sep 13, 2026
09f184c
fix(slack): preserve workspace context for question continuations
tbiyss Sep 13, 2026
800ebb7
Merge remote-tracking branch 'origin/beta' into feature/agent-questio…
tbiyss Sep 13, 2026
b498b87
Merge branch 'feature/agent-question-forms' into beta
tbiyss Sep 13, 2026
2e1da92
Fix membership query encoding for Slack question cards
tbiyss Sep 13, 2026
065026c
Merge branch 'fix/question-members-encoding' into beta
tbiyss Sep 13, 2026
8e7ad48
fix: refresh Codex pricing and usage history
tbiyss Sep 13, 2026
319ff98
merge: refresh Codex pricing and history
tbiyss Sep 13, 2026
8d62512
fix: honor Codex pricing effective dates
tbiyss Sep 13, 2026
78f9f24
merge: honor Codex pricing effective dates
tbiyss Sep 13, 2026
5db1dca
fix: remove successfully processed media uploads
tbiyss Sep 14, 2026
dea082e
merge: remove processed media uploads
tbiyss Sep 14, 2026
93a3826
feat: warn about shared working folders
tbiyss Sep 14, 2026
54eca90
merge: warn about shared working folders
tbiyss Sep 14, 2026
0657007
test: document pinned browser acceptance setup
tbiyss Sep 14, 2026
f1d9a09
merge: document pinned browser acceptance setup
tbiyss Sep 14, 2026
f0b32a9
feat(slack): preview answer images
tbiyss Sep 17, 2026
d7dcfa0
merge: preview Slack answer images
tbiyss Sep 17, 2026
902d492
feat: add admin-only sudo threads
tbiyss Sep 17, 2026
b218c6f
feat: add opt-in isolated VPN database services
tbiyss Sep 17, 2026
e671e8b
fix(slack): attach workspace images natively
tbiyss Sep 17, 2026
ed1b380
merge: attach Slack answer images natively
tbiyss Sep 17, 2026
dcb8bcf
fix: fail over on plain Codex capacity errors
tbiyss Sep 17, 2026
cf12892
merge: recover from plain Codex capacity errors
tbiyss Sep 17, 2026
a1396e4
merge: refresh VPN service against beta
tbiyss Sep 17, 2026
53ee6bc
merge: add opt-in isolated VPN services
tbiyss Sep 17, 2026
30eda95
feat: make quiet-thread reminders personal
tbiyss Sep 17, 2026
266ba00
merge: make quiet-thread reminders personal
tbiyss Sep 17, 2026
bc51f79
docs: clarify personal reminder ownership
tbiyss Sep 17, 2026
db9da7d
merge: clarify personal reminder ownership
tbiyss Sep 17, 2026
b99deb2
merge: reconcile stable VPN backport
tbiyss Sep 18, 2026
efaa47e
feat: control configured channel VPN from chat and settings
tbiyss Sep 18, 2026
1d6c284
merge: channel VPN controls
tbiyss Sep 18, 2026
a10b2c3
fix: provision rclone for Drive sync
tbiyss Sep 20, 2026
7ff2d23
merge: provision rclone for Drive sync
tbiyss Sep 20, 2026
9904f1c
feat: run channel VPNs with OpenVPN 3 and scoped database reads
tbiyss Sep 21, 2026
9b5af71
test: record OpenVPN 3 and channel database acceptance
tbiyss Sep 21, 2026
01cdf36
merge: run channel VPNs with OpenVPN 3
tbiyss Sep 21, 2026
71a14f6
feat: add an opt-in Qwen harness driven through Claude Code
tbiyss Sep 21, 2026
a2d4644
merge: add an opt-in Qwen harness driven through Claude Code
tbiyss Sep 21, 2026
f41986c
fix: give Qwen its own Settings card and flag a missing provider key
tbiyss Sep 21, 2026
46cc85a
fix: drop unusable MCP selections instead of failing the turn
tbiyss Sep 21, 2026
4460354
merge: drop unusable MCP selections instead of failing the turn
tbiyss Sep 21, 2026
3bbb254
merge: promote beta for the 0.5.1 release
tbiyss Sep 21, 2026
d5607e8
fix: default Qwen runs to a Qwen model and keep permission refusals p…
tbiyss Sep 21, 2026
b1e5985
merge: default Qwen runs to a Qwen model and keep permission refusals…
tbiyss Sep 21, 2026
580ae40
merge: promote beta for the 0.5.1 release
tbiyss Sep 21, 2026
bf8ffb4
docs: date the 0.5.1 release and record its acceptance evidence
tbiyss Sep 21, 2026
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
40 changes: 25 additions & 15 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,11 +9,12 @@ Contributor workflow).
A **self-hosted Linux daemon** (Node ESM, Express, `node:sqlite`) that runs coding agents inside
team chat. Every conversation — a Slack DM, group or channel; Microsoft Teams and Google Chat in
Beta — gets its **own work folder** (`~/ChannelGate/<platform>/<slug>/`), its **own rootless
Podman container**, its own persistent memory and one engine session per thread. Each message runs
Podman container**, its own persistent memory and one engine session per thread. Each ordinary message runs
a **headless engine turn inside that container**: Claude Code (`claude -p`, the primary engine),
OpenAI Codex (`codex exec`), or OpenCode (a proof adapter admitted only read-only and
network-off). The container is the confinement boundary; the folder's `.claude/settings.json`
carries the tool policy. A built-in admin web UI (same process) manages users, channels, engines,
network-off). An organization admin can explicitly turn one Slack thread into a direct-host
`/sudo` thread; only admins may then message it. The container is the default confinement boundary;
the folder's `.claude/settings.json` carries the tool policy. A built-in admin web UI (same process) manages users, channels, engines,
MCP connections, skills, per-channel secrets, schedules, usage and the license. `src/ee/` is the
proprietary licensing plane (see the rules).

Expand All @@ -38,13 +39,14 @@ Gateway daemon
│ license admission (src/ee/limits.js), then provision ~/ChannelGate/<platform>/<slug>/
│ (.claude/settings.json lockdown, managed CLAUDE.md block, gateway-usage + channel-memory skills,
│ granted catalog skills)
│ ensure the channel's container is up (rootless Podman: HOME volume, work folder + clean workspace
│ + artifact dir at identical paths, control socket read-only, bridge network)
│ resolve runtime: normal → ensure the channel container; admin-authenticated `/sudo` thread → host
│ (container: HOME volume, work folder + clean workspace + artifact dir at identical paths,
│ control socket read-only, bridge network; host: daemon OS account and native HOME/toolchain)
│ resolve session: thread key → engine session id (resume) | fresh (memory catalog prepended)
│ build MCP config: gateway control (socket bridge) + composio-user (author) + composio-agent
│ (channel → org token) + selected catalog/plugin servers → a 0600 file in the artifact dir
▼
exec inside the container (Claude, cold):
exec in the resolved runtime (Claude, cold):
claude -p <text> --output-format stream-json --verbose --include-partial-messages
--setting-sources "" --settings <lockdown> --append-system-prompt-file <CLAUDE.md>
[--model M] [--effort E] (--session-id <new> | -r <id>) --mcp-config <file> --strict-mcp-config
Expand Down Expand Up @@ -76,7 +78,7 @@ post/edit the reply in the thread (degraded to the surface's capabilities) → u
the changed keys), `dead-fields.js` (retired fields stripped on every write).
- `src/db/` — `index.js` (the one lazy `node:sqlite` connection: WAL, `busy_timeout`,
`foreign_keys`, migrations on open, the one-time legacy JSON import behind `_meta` flags),
`migrations.js` (versioned on `PRAGMA user_version`, currently 25 — append, never edit),
`migrations.js` (versioned on `PRAGMA user_version`, currently 26 — append, never edit),
`import-legacy.js`, `fts.js` (the optional FTS5 `channel_memory_fts` index; without FTS5 memory
search degrades to a scan).
- `src/gateway/run.js` — the run orchestrator: engine adapter selection and precedence (per-run
Expand Down Expand Up @@ -201,8 +203,9 @@ post/edit the reply in the thread (degraded to the surface's capabilities) → u
close a cycle through `config/settings.js`.
- `src/runtimes/` — WHERE an engine process runs: `contract.js` (the RuntimeBackend contract),
`resolve.js` (the one place that builds the RuntimeTarget a turn, a background job and the
memory reviewer each receive at their OWN spawn), `registry.js` (registers the container backend
and nothing else; `local.js` is the unregistered host spawner kept for direct-runner tests) and
memory reviewer each receive at their OWN spawn), `registry.js` (registers the default container
backend plus the admin-authenticated `/sudo` host backend; `local.js` remains an unregistered
daemon-internal spawner kept for direct-runner tests), `host.js` (direct daemon-account spawn) and
`container/` — the rootless Podman backend: the CLI probe (`podman`, then `docker`), the image
(expected version + digest over every file in `containers/` compared with the built image's
labels; a missing or stale image fails the run closed with the `npm run build:image` remedy),
Expand Down Expand Up @@ -308,7 +311,7 @@ through the control MCP.
`thread_overrides`, `conversation_reply_sessions`, `active_runs`, `stopped_turns`,
`inbound_events`, `teams_graph_subscriptions`); automation (`schedules`, `acks`,
`followup_threads`, `followup_done`, `followup_digest_messages`, `bg_jobs`, `api_jobs`);
approvals (`approval_requests`, `approval_link_tokens`); skills (`skills`, `skill_revisions`,
approvals and questions (`approval_requests`, `approval_link_tokens`, `question_requests`); skills (`skills`, `skill_revisions`,
`skill_revision_files`, `skill_sources`, `skill_templates`, `skill_usage`, `skill_proposals`,
`skill_access_tokens`); Composio SDK (`composio_sessions`); licensing (`license_usage`);
dashboard data (`usage`, `usage_components`, `usage_requests`, `usage_repair_batches`,
Expand All @@ -327,14 +330,15 @@ Config that stays as **files** (read wholesale / bootstrap, hand-editable):
- `~/.channelgate/config/gateway-usage/` — per-file overrides of the `gateway-usage` skill.
- `~/.channelgate/channels/<platform>/<slug>/.claude/settings.json` — the per-channel lockdown
contract Claude Code itself reads (must be a file): tool permissions, the MCP allowlist,
memory-off and the Stop hook. No `sandbox` block — the container is the boundary.
memory-off and the Stop hook. No `sandbox` block — the container is the ordinary-run boundary;
a sudo-host turn is explicitly outside it.
- `containers/versions.json` — the image contract: the spec version and every toolchain pin
(`docs/COMPATIBILITY.md`; the nightly canaries read the same file).

## Non-negotiable rules

- **Confinement is the product, and the container is the boundary.** Every turn — foreground,
background job, schedule, API run, memory review — runs inside the channel's own container
- **Confinement is the product, and the container is the default boundary.** Every ordinary turn —
foreground, background job, schedule, API run, memory review — runs inside the channel's own container
(rootless Podman, image-shipped toolchain, `--cap-drop ALL`, no `sudo`): a per-channel HOME
volume at `/home/agent` (engine sessions, CLI logins, installed tools) and, bind-mounted at
their identical absolute paths, ONLY the channel's work folder, its clean workspace and its
Expand All @@ -350,8 +354,14 @@ Config that stays as **files** (read wholesale / bootstrap, hand-editable):
the planned follow-up. Every channel folder still gets the lockdown file
(`autoMemoryEnabled:false`, `autoDreamEnabled:false`, curated `permissions.allow`, the MCP
allowlist, the Stop hook) — it carries POLICY, never a `sandbox` block, and nothing a run can
do changes what its container mounts. Never exec an engine outside a container. Admin channels
run in containers too: the admin author's live turn adds the bypass flag, and the work folder is
do changes what its container mounts. The one process-boundary escape hatch is typed Slack
`/sudo`, scoped to exactly one thread: only a current organization admin can enable or disable it,
every sender and background launch is re-authorized as an admin, non-admin messages are rejected
before hydration or process spawn, and the resolved engine runs directly as the daemon OS user
with its host filesystem, processes, HOME, commands and network. The flag is a thread posture,
never reusable authority; stored channel metadata and run-API overrides cannot select the host.
Turning it off restores the container, and native session state is carried across the boundary
when possible. Admin channels otherwise run in containers too: the admin author's live turn adds the bypass flag, and the work folder is
mounted read-write like any other's — so an admin channel whose work folder is a host directory
(the gateway's own checkout, say) hands that directory, and only that directory, to its
container, everything in it included. That is the intended trust model for admin channels; put
Expand Down
106 changes: 88 additions & 18 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,17 +1,105 @@
# Changelog — ChannelGate

ChannelGate was formerly *Claude Gateway for Slack*; entries below the rename keep their original
wording. All notable changes to the gateway, newest first. Dates are when the work landed.
This project brings Claude Code (and optionally OpenAI Codex) into Slack as a self-hosted,
per-channel-sandboxed agent. See `FEATURES.md` for the living catalog and `docs/WHY.md` for the
product overview.

> **Publication dates.** Every public release entry below carries the date the Licensor published
> it. Entries for work that never left the private repository are not publications. No version is
> relicensed automatically (license v1.2 removed the former Change Date before first publication).
>
> | License version | Effective | Published |
> | --- | --- | --- |
> | Makeitfuture Sustainable Use License 1.2 | 2026-08-25 | 2026-09-06 (with ChannelGate 0.5.0) |
> | Makeitfuture Sustainable Use License 1.1 | 2026-08-20 | never published |
> | Makeitfuture Sustainable Use License 1.0 | 2026-08-06 | never published |

## 0.5.1 — 2026-09-21

- Fix two release-smoke defects. A Qwen run with no model configured now asks QwenCloud for a
Qwen model instead of the CLI's Anthropic default (which failed every such turn with "Model not
exist"). A tool request from an HTTP run-API turn is now refused with a readable reason instead
of Claude Code's "invalid permission result" error; the request stays denied either way.

- Stop an unusable Cloud MCP selection from failing a conversation. A selected optional MCP server
that is missing from the host configuration, needs a host credential, or no longer matches the
transport it was selected with is now dropped from that run — for Claude, Qwen and Codex, on the
primary and the failover engine alike — instead of ending the turn. The answer is prefixed with
which connections were skipped and why, and the drop is recorded as an event for admins. Host
credentials are still never relayed into a channel container, and a corrupt operator
configuration no longer blocks every channel that selected anything.

- Add an optional **Qwen (Claude Code)** harness: the same Claude Code CLI driven against
QwenCloud's Anthropic-compatible endpoint, with its own gateway-level API key and base URL, and
a model list read live from the account. It is off until an admin turns it on, after which it
appears in the admin engine selectors, the Slack channel settings modal and `/model`. No
Anthropic credential ever reaches a Qwen run, it is outside the automatic failover in both
directions, and its turns are recorded with real tokens and no invented dollar cost.

- Run prepared channel VPNs with OpenVPN 3 Linux and refresh their protected supervisors on ON.
Add channel-scoped, bounded read-only database operations for Claude and Codex, without granting
ordinary agent containers VPN privileges or exposing database credentials.

- Provision the host-side `rclone` dependency during fresh deployments and repair it during an
update even when the checkout is already current. User services install it in `~/.local/bin`,
downloads are checksum-verified, and Google Drive connection tests retry an earlier missing-tool
result without requiring a daemon restart.

- Control a prepared channel VPN through the agent, the web channel Network controls, and Slack
Settings → Network. Managers/admins can turn it on/off; status distinguishes connecting from
connected and reports safe certificate/authentication errors. Stopping also cleans up manual starts.

- Move quiet-thread reminders from conversation settings to a personal user preference. Each
reminder now follows and mentions the requester across channels and DMs; users can turn it on or
off in Slack App Home, while admins can manage individual or organization-default choices.

- Add an optional operator-managed OpenVPN/MySQL service per channel, with dedicated tunnel
privileges, database-only routing/firewall, protected channel-secret references, a persistent
user service and read-only verification. Ordinary chat containers retain their existing rights.

- Fix Codex capacity refusals delivered as plain-text `turn.failed` events. They are now classified
as transient provider failures, retried in place, and automatically handed to the other enabled
harness when capacity remains unavailable.

- Add an organization-admin-only `/sudo` posture for individual Slack threads. While enabled,
admin messages and their background work execute directly on the gateway host as the daemon OS
user; non-admin messages are rejected as sudo-thread traffic before work starts. `/sudo off`
restores the normal per-conversation container, with Claude/Codex session state carried across
the boundary when possible. The host runtime cannot be selected through channel metadata or the
run API, and every turn rechecks current admin authority.

- Render up to five public images referenced in an agent's Slack answer as native Block Kit image
previews, while preserving clickable Markdown fallbacks and completed text delivery when Slack
rejects a preview. Images referenced from the channel workspace are now uploaded into the thread
as native Slack files instead, so they get the same inline thumbnail, download control and full
preview as a human attachment.

- Highlight every conversation that shares its resolved working folder with another conversation
in red in the Admin UI, and warn in red while browsing a folder that is already assigned elsewhere.

- Remove gateway-downloaded audio after a successful local or Slack-fallback transcript, while
retaining failed inputs for retry and refusing symlinks or paths outside managed uploads. Teach
the built-in video-understanding workflow to remove only successfully processed uploaded source
videos after all required re-sampling, never project files or failed inputs.

- Correct Codex history repricing to honor OpenAI's effective date for GPT-5.6 Sol: retain the
original $5 / $0.50 cached / $30 rate before 2026-08-21 and apply $4 / $0.40 / $20 from that
date onward. Upgraded instances rerun the backup-first correction under a new pricing basis.

- Refresh Codex Standard API-equivalent pricing from official OpenAI documentation: add
GPT-6 Astra at $10 / $1 cached / $50 per million tokens and reduce GPT-5.6 Sol (plus its
`gpt-5.6` alias) to $4 / $0.40 / $20. Keep the fallback picker aligned with the current Codex
CLI catalog, leave CLI-only Spark explicitly unpriced until an official rate exists, and
automatically back up and reprice component/request history since 2026-07-13 once on upgrade.

- Fix Slack question-card posting by encoding channel membership checks as GET query parameters.

- Let agents ask clarification questions with Slack cards and paged forms: custom option buttons,
Yes/No, multiple selections, and written answers. Save drafts until submission, retain pending
questions across restarts, and continue the requester's thread after they submit.

- Keep sidebar update messages inside the rail, wrapping long details and showing a short commit
revision with the full hash on hover.

Expand Down Expand Up @@ -59,24 +147,6 @@

- Fix one-time automation edits shifting by the browser/daemon timezone difference and potentially firing future tasks immediately.

ChannelGate was formerly *Claude Gateway for Slack*; entries below the rename keep their original
wording. All notable changes to the gateway, newest first. Dates are when the work landed.
This project brings Claude Code (and optionally OpenAI Codex) into Slack as a self-hosted,
per-channel-sandboxed agent. See `FEATURES.md` for the living catalog and `docs/WHY.md` for the
product overview.

> **Publication dates.** Every public release entry below carries the date the Licensor published
> it. Entries for work that never left the private repository are not publications. No version is
> relicensed automatically (license v1.2 removed the former Change Date before first publication).
>
> | License version | Effective | Published |
> | --- | --- | --- |
> | Makeitfuture Sustainable Use License 1.2 | 2026-08-25 | 2026-09-06 (with ChannelGate 0.5.0) |
> | Makeitfuture Sustainable Use License 1.1 | 2026-08-20 | never published |
> | Makeitfuture Sustainable Use License 1.0 | 2026-08-06 | never published |

## 0.5.1 — Unreleased

- Manage plugin packages through existing skill sources, review, templates and channel grants.
Compile native Claude hooks as an inline event map so SessionStart hooks execute correctly;
keep executable hooks restricted to authorized live admin turns.
Expand Down
Loading
Loading