Local-server mode + macOS support (auto-detected) — builds on #28 - #31
Merged
Conversation
bborn
added a commit
that referenced
this pull request
Jun 9, 2026
notifications.jsonl survives as the push substrate, but the channels refactor changes three things: copy taskyou-channel.ts (bun/TS) instead of linear-poll.mjs; reuse assigned_gm (#32 + workflow#561) for per-GM routing (answers the shared-bot vs per-user question); adopt the IS_LOCAL/SSH runRemote pattern + OS-aware hooks dir (#31). Adds a dependencies section and merge order (#28 -> #31/#32 -> Slack). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
bborn
force-pushed
the
feat/channels-integration
branch
from
June 9, 2026 12:35
8caee9e to
188555e
Compare
bborn
force-pushed
the
pr/local-mode-macos
branch
from
June 9, 2026 12:35
a951d00 to
d28e3bc
Compare
PR taskyou#28 assumes a remote Linux server reached over SSH. These additive changes let the GM run on the same machine as the TaskYou daemon (including macOS) with zero change to the existing remote-Linux flow: - channel: runRemote() gains a LOCAL branch (bash -lc, no SSH) when SERVER_HOST is local/localhost/empty; the SSH path is unchanged. - setup.sh: OS-detect the hooks dir to match ty's os.UserConfigDir() (Linux ~/.config/task/hooks vs macOS ~/Library/Application Support/ task/hooks). The hardcoded ~/.config path silently misfired on macOS. - setup.sh: a local-server provisioning path that skips SSH/systemd when the daemon runs on this same box; remote-Linux/systemd intact. - config.example.env: document SERVER_HOST=local. Both paths are auto-detected; no existing behavior is removed. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Local mode registers projects and installs hooks against a ty daemon assumed to run on this same machine. If ty is missing, fail loudly instead of erroring mid-registration; if the daemon isn't running, warn — otherwise setup 'succeeds' but no task events ever fire. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
bborn
force-pushed
the
pr/local-mode-macos
branch
from
June 9, 2026 12:36
d28e3bc to
5a64acd
Compare
bborn
added a commit
that referenced
this pull request
Jun 9, 2026
#28 (channels) and #31 (local/macOS) are merged to main, so the design is concrete: a Slack module is taskyou-channel.ts with its two ends re-pointed — reuse pollNotifications() + runRemote() verbatim; net-new is just a Slack adapter + LLM classifier. Ship outbound (hook -> Slack webhook) first, inbound control second. Removes the assigned_gm / per-GM scoping dependency (out of scope: single bot, single operator). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
bborn
added a commit
that referenced
this pull request
Jun 9, 2026
notifications.jsonl survives as the push substrate, but the channels refactor changes three things: copy taskyou-channel.ts (bun/TS) instead of linear-poll.mjs; reuse assigned_gm (#32 + workflow#561) for per-GM routing (answers the shared-bot vs per-user question); adopt the IS_LOCAL/SSH runRemote pattern + OS-aware hooks dir (#31). Adds a dependencies section and merge order (#28 -> #31/#32 -> Slack). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
bborn
added a commit
that referenced
this pull request
Jun 9, 2026
#28 (channels) and #31 (local/macOS) are merged to main, so the design is concrete: a Slack module is taskyou-channel.ts with its two ends re-pointed — reuse pollNotifications() + runRemote() verbatim; net-new is just a Slack adapter + LLM classifier. Ship outbound (hook -> Slack webhook) first, inbound control second. Removes the assigned_gm / per-GM scoping dependency (out of scope: single bot, single operator). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This was referenced Jun 9, 2026
bborn
added a commit
that referenced
this pull request
Jun 9, 2026
* docs: add Slack module design (manage TaskYou from Slack) From TaskYou task #3723. Proposes modules/slack/, modeled on modules/linear/linear-poll.mjs: watch a Slack channel/DM/@mention, classify intent, drive ty, post replies, and tail notifications.jsonl to push task.blocked/completed back to the channel. Enabled via SLACK_ENABLED. Separates the chat-control bridge (recommended) from the harder hosted/remote-MCP path (deferred to the credential-proxy work). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * docs: reconcile Slack design with in-flight channels work (#28/#31/#32) notifications.jsonl survives as the push substrate, but the channels refactor changes three things: copy taskyou-channel.ts (bun/TS) instead of linear-poll.mjs; reuse assigned_gm (#32 + workflow#561) for per-GM routing (answers the shared-bot vs per-user question); adopt the IS_LOCAL/SSH runRemote pattern + OS-aware hooks dir (#31). Adds a dependencies section and merge order (#28 -> #31/#32 -> Slack). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * docs: land Slack design on merged channel work; drop per-GM scoping #28 (channels) and #31 (local/macOS) are merged to main, so the design is concrete: a Slack module is taskyou-channel.ts with its two ends re-pointed — reuse pollNotifications() + runRemote() verbatim; net-new is just a Slack adapter + LLM classifier. Ship outbound (hook -> Slack webhook) first, inbound control second. Removes the assigned_gm / per-GM scoping dependency (out of scope: single bot, single operator). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * feat(slack): implement two-way Slack module (modules/slack) Builds the Slack integration end to end (outbound + inbound), per the design in docs/plans/2026-06-08-slack-module-design.md. modules/slack/slack-bridge.mjs — zero-dependency Node daemon (raw fetch + global WebSocket, like the Linear poller). Runs on the agents server next to ty + notifications.jsonl: • Outbound: tails notifications.jsonl → chat.postMessage (blocked/ completed/failed/started). Slack-originated tasks answer in-thread; everything else goes to SLACK_NOTIFY_CHANNEL. • Inbound: Slack Socket Mode (no public URL) → allowlist check → intent classification (Anthropic API, with a keyword-heuristic fallback when no key) → ty create/execute/input/list. Replies in-thread. Also: unit tests for the pure logic (node --test, 11 cases), a README, the ty-slack systemd service template, and setup.sh wiring (server + exe.dev install the service; local mode renders files + .env) plus SLACK_* in config.example.env and the README modules list. The module is gated behind SLACK_ENABLED=true. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * feat(slack): default classifier to on-box `claude` CLI + runaway guards Classifier no longer requires a separate ANTHROPIC_API_KEY: it prefers the already-authenticated on-box `claude` CLI (claude -p — the same primitive the GM/executors use), falls back to the Anthropic API if a key is set, then a keyword heuristic. Override via SLACK_CLASSIFIER. Loop / token-burn hardening (per review): - claude -p sandboxed per call: --strict-mcp-config --mcp-config {} + --disallowedTools … keep it to a single turn (no agentic spiral); --max-budget-usd (default $0.05) hard-caps cost; timeout + maxBuffer; no retry. - Self-loop guards: ignore own/bot/edited messages (bot_id/subtype/BOT_USER_ID). - Concurrency cap (SLACK_MAX_CONCURRENT, default 3) bounds in-flight classifications; excess messages declined, not queued. - Socket generation guard: only the newest connection's handlers stay live, so a reconnect race can't double-deliver events. - Outbound poller: per-tick cap (SLACK_MAX_NOTIFS_PER_POLL, default 25) with byte-accurate gradual drain; only complete lines consumed (partial hook line held); lost/corrupt state skips backlog instead of replaying. Tests: +2 (buildClassifierContext, readNewChunk partial-line/rotation) → 13/13. Docs: README "Runaway / cost protection", config knobs, design-doc note. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
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
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
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.
Builds on #28 (
feat/channels-integration) — this PR is stacked on that branch, so the diff here is just these additions (3 files, +148/−13). Happy to fold them in as commits on #28 directly instead if you'd prefer; once #28 lands onmainI'll retarget this tomain.Summary
#28 adds the push-based task-event channel, built for a remote Linux server reached over SSH. This makes the same channel also work when the GM runs on the same machine as the daemon — including macOS — auto-detected, with the remote-Linux/SSH flow left fully intact. Everything is additive; nothing existing is removed.
What changed
1. Channel local mode
templates/channel/taskyou-channel.ts.tmpl—runRemote()gains aLOCALbranch: whenSERVER_HOSTislocal/localhost/ empty (IS_LOCAL), commands run viabash -lcdirectly on this machine (no SSH). Otherwise the original SSH path runs unchanged.~/.local/binis prepended toPATHand wecdintoSERVER_HOME, mirroring the remote path.2. Cross-platform hooks directory
setup.sh— the hooks dir was hardcoded to~/.config/task/hooks, butty'shooks.DefaultHooksDir()follows Go'sos.UserConfigDir():~/.config/task/hooks~/Library/Application Support/task/hooksA
resolve_hooks_dirhelper detects the OS viauname -sand picks the right path. Linux installs are byte-identical; macOS installs now land where the daemon actually looks (previously, hooks placed in~/.configon a Mac silently never fired).3. Local-server setup mode
setup.sh—setup_server()detects a local server (SERVER_HOSTlocal/localhost/empty) and delegates to a newsetup_server_local()that configures git identity, registers project repos withtylocally, installs hooks to the OS-correct dir, createsnotifications.jsonl, and skips SSH provisioning + the systemd service (GM and daemon share the box). The remote-Linux (setup_server) and exe.dev (setup_exe_dev) paths are untouched aside from adopting the OS-aware hooks dir.Plus
config.example.envdocumentsSERVER_HOST=local.Testing (macOS)
bun buildbundles cleanly andtsc --noEmit --strictis clean; the repo's ownsmoke-test.tspasses against the local render.bash -lcrunner drivestycommands without SSH.~/Library/Application Support/task/hooksand fire on task events.elsebranches only — near-zero deletions).Compatibility
Fully backward-compatible. Existing remote-Linux deployments behave exactly as before; the new behavior only engages when
SERVER_HOSTis local/localhost/empty or the target OS is macOS.