feat(infinity-slack-bot): create a Slack bot - #43
Open
akainth015 wants to merge 11 commits into
Open
Conversation
akainth015
force-pushed
the
push-ssyrmkszpxwn
branch
from
June 18, 2026 21:18
08340d5 to
5a082dd
Compare
Deploying infinity with
|
| Latest commit: |
658b599
|
| Status: | ✅ Deploy successful! |
| Preview URL: | https://6b7895dd.infinity-dc7.pages.dev |
| Branch Preview URL: | https://push-ssyrmkszpxwn.infinity-dc7.pages.dev |
akainth015
force-pushed
the
push-ssyrmkszpxwn
branch
2 times, most recently
from
June 18, 2026 21:25
a0573a9 to
6885d51
Compare
shadaj
reviewed
Jun 18, 2026
shadaj
reviewed
Jun 18, 2026
| infinity --daemon | ||
|
|
||
| # Start the Slack bot | ||
| infinity-slack-bot |
Member
There was a problem hiding this comment.
I wonder if similar to the providers plugin system, we should have a channels plugin system so that the daemon auto-spawns the Slack bridge and provides it the appropriate socket path?
akainth015
force-pushed
the
push-ssyrmkszpxwn
branch
8 times, most recently
from
June 23, 2026 22:07
463bcdb to
789deee
Compare
akainth015
marked this pull request as ready for review
June 23, 2026 22:41
shadaj
reviewed
Jun 27, 2026
| slack_events: Stream<SlackEvent, Process<'a, P>, Unbounded>, | ||
| daemon_events: Stream<DaemonEvent, Process<'a, P>, Unbounded>, | ||
| ) -> ( | ||
| Stream<SlackAction, Process<'a, P>, Unbounded, NoOrder>, |
Member
There was a problem hiding this comment.
This seems quite suspicious to me, since the Slack API will care about the order, and the daemon too. So they should probably be totally ordered outputs but have some internal nondets?
akainth015
force-pushed
the
push-ssyrmkszpxwn
branch
from
July 7, 2026 22:22
789deee to
483c674
Compare
akainth015
force-pushed
the
push-ssyrmkszpxwn
branch
from
July 24, 2026 21:53
483c674 to
85e53e8
Compare
Co-authored-by: Infinity 🤖 <infinity@hydro.run>
…ondet! guards
* Replaced `merge_unordered` with `merge_ordered` for both output streams,
marking them as `TotalOrder` (the default) to signal that ordering is
significant to downstream consumers (Slack API, daemon).
* Added `nondet!` guards documenting the sources of non-determinism:
- `daemon_commands`: button commands vs message commands may interleave
non-deterministically due to batching.
- `slack_actions`: daemon-sourced actions vs status actions originate from
independent event sources with no causal ordering between them.
* Removed the `NoOrder` import since it's no longer used.
* Return type simplified to `Stream<..., Unbounded>` (defaults to TotalOrder).
Co-authored-by: Infinity 🤖 <infinity@hydro.run>
…se appears in a new message When a button is clicked, the bot now emits a `StreamStop` action for the thread before the daemon processes the choice. This closes any active streaming message, so subsequent `TextChunk` events from the daemon start a fresh stream (new Slack message) rather than appending to the preceding one. * Clone `button_clicks` and map it to `SlackAction::StreamStop` actions * Merge these stop actions into the outbound `slack_actions` stream * The `StreamStop` handler in the sidecar removes the thread from `active_streams`, causing the next `StreamAppend` to call `start_fresh` and create a new message Co-authored-by: Infinity 🤖 <infinity@hydro.run>
…ter click
When a user clicks a button choice, the original message containing the buttons
is now updated via `chat.update` to show "✅ Selected: *{choice}*" instead of
the interactive buttons. This provides clear feedback on which option was chosen.
Changes:
* Add `message_ts` and `button_text` fields to `SlackEvent` to carry the button
message's timestamp and the clicked button's label text
* Add `InteractiveText` struct and parse `text` from Slack's interactive action payload
* Add `SlackAction::UpdateMessage` variant for updating existing messages
* Add `UpdateMessageRequest` and `SlackClient::update_message()` calling `chat.update`
* In the dataflow, map button clicks to `UpdateMessage` actions that replace the
button blocks with a static mrkdwn section showing the selection
* Handle `UpdateMessage` in the sidecar outbound loop
* Update all tests to include the new `SlackEvent` fields
Co-authored-by: Infinity 🤖 <infinity@hydro.run>
…teSession and add tests The previous change only honored `keeps_session_alive` on `ClientMessage::Connect`, but the Slack bot's primary flow goes through `CreateSession` + `UserInput`, both of which attached the client with a hardcoded keep-alive `true`. Slack-created sessions therefore still kept sessions warm. Fixes: * `infinity-protocol`: Added `keeps_session_alive` (serde default `true`) to `ClientMessage::CreateSession` as well. * `client_handler.rs`: Tracks the connection-level `connection_keeps_alive` state (set by `CreateSession`/`Connect`) and threads it through `attach_client` and `send_input`, so re-attachment after a session restart preserves the client's declared keep-alive behavior. * `infinity-slack-bot`: `create_session` now sends `keeps_session_alive: false`. * Fixed a lost-input race exposed by non-keep-alive connections: when a session idles out, its wrapper task takes time to wind down (RAP server cleanup), during which `send_input` saw the agent task as still running and dropped inputs into the dead agent loop. `Session` now exposes the `worker_shutdown` cancellation token; `send_input` treats a cancelled token as needing a restart and re-checks synchronously right before sending (with one retry), so inputs arriving mid-wind-down transparently restart the session instead of vanishing. Tests (`crates/infinity-daemon/tests/keep_alive.rs`): * `non_keep_alive_client_does_not_keep_session_warm`: a connected Slack-style client must not prevent idle exit (verified to fail against the old hardcoded-true behavior). * `keep_alive_client_keeps_session_warm`: normal clients still keep sessions warm. * `input_after_idle_exit_restarts_session`: follow-up input on the same connection after idle exit restarts the session and gets a response. Co-authored-by: Infinity 🤖 <infinity@hydro.run>
…am age splitting Three fixes for the Slack bot's interactive and streaming behavior: ## Button dismissal when choice is resolved without a click Previously, when a user choice (e.g. tool approval buttons) was resolved by means other than a Slack button click — such as auto-selection by another client, timeout, or interruption — the button message in Slack remained interactive indefinitely. Clicking a stale button would silently fail. * Added `choice_messages` map to runtime: `choice_id → (channel, message_ts)` * Extended `SlackAction::PostBlocks` with optional `choice_id`; sidecar stores the resulting message_ts when posting choice buttons * Added `SlackAction::DismissChoiceButtons` variant; on `UserChoiceComplete` from the daemon, the sidecar replaces buttons with "⏭️ Choice resolved automatically" * On button click, the entry is removed from tracking so the subsequent `UserChoiceComplete` is a no-op ## Tool call indicator indentation after lists The tool call indicator (🔧 `name(…)`) was rendered indented when it followed a numbered or bulleted list, because Slack mrkdwn treated a single `\n` as a list-item continuation. * Changed the tool call prefix from `\n` to `\n\n` to insert a paragraph break that terminates any active list context ## Stream age splitting Long-running responses previously accumulated into a single streaming message until it hit the 20k character limit. * Added `started_at` to `StreamState` and a `MAX_STREAM_AGE` of 3 minutes. Once a stream is older than that, the next clean breaking point (chunk containing a newline, not inside a code block) stops the current message and starts a new one — same mechanism as the existing size-based split. Co-authored-by: Infinity 🤖 <infinity@hydro.run>
Follows https://docs.slack.dev/ai/developing-agents for an idiomatic agent UX. * **Manifest (docs)**: migrate `assistant_view` → `agent_view` (with `agent_description`), add explicit `app_home` messages-tab settings, and subscribe to `app_home_opened` + `app_context_changed`. Warning callouts added: manifests replace (not merge) settings, and the `agent_view` switch is irreversible. * **Tool calls as task cards**: `ToolCall` now streams a `task_update` chunk (`in_progress`) instead of an inline `🔧` text line; `ToolResult` completes the oldest in-flight task. Pending tasks are drained (marked complete) on stream stop so messages never finalize with spinners. Falls back to the plain-text indicator if the workspace rejects task chunks. * **Thread titles**: `assistant.threads.setTitle` is called when the daemon reports a session title (on `Connected` and via `SessionsUpdated`), deduped through a runtime title cache. * **Suggested prompts**: `app_home_opened` (tab == "messages") pins starter prompts to the top of the Messages tab, once per user per run. * **Content disclaimer**: streams finalize via `chat.stopStream` with an AI-generated-content context block, with fallback to a plain stop. * Runtime gains `tool_tasks`, `tool_task_seq`, `thread_titles`, and `app_home_seen` state; `SlackEvent` gains `is_app_home_opened`. * Tests updated for task updates; new parsing tests for `app_home_opened`. Not implemented (follow-up): feedback buttons (`context_actions` / `feedback_buttons`) — needs an interaction handler that doesn't collide with choice-button parsing, and a feedback sink to record results. Co-authored-by: Infinity 🤖 <infinity@hydro.run>
Previously `UserChoiceRequired` posted the choice buttons as a new message but left the active stream open, so output produced after the choice continued appending to the pre-choice message (which sits above the buttons) instead of starting a new message. * `UserChoiceRequired` now emits `StreamStop` followed by `PostBlocks`, so the pre-choice message is finalized (and buffered text flushed) before the buttons are posted and any subsequent output begins a fresh message. `StreamStop` is a no-op when no stream is active. Co-authored-by: Infinity 🤖 <infinity@hydro.run>
akainth015
force-pushed
the
push-ssyrmkszpxwn
branch
from
July 31, 2026 21:44
53ff2aa to
61ae7ac
Compare
akainth015
added a commit
that referenced
this pull request
Jul 31, 2026
The CI "Lint" check on PR #43 (infinity-slack-bot) was failing because the `THIRD-PARTY` license file was stale. The `cargo about generate` step detected differences between the committed file and what the current `Cargo.lock` produces. Applied the exact diff from the CI run to update `THIRD-PARTY`: - Removed stale `windows-*` v0.53 and `windows-sys` v0.60.2 entries (dependencies that were pruned from the lock file) - Added new license entries for transitive dependencies brought in by `infinity-slack-bot`: `sealed 0.6.0`, `toml_datetime 1.1.1`, `toml_edit 0.25.13`, `toml_parser 1.1.3`, and many others (tokio-tungstenite's dependency tree, hydro_lang's dependencies, etc.) - Added new license text sections (Apache-2.0 for sealed, MPL-2.0 for new crates, MIT for various new transitive deps) Co-authored-by: Infinity 🤖 <infinity@hydro.run>
The previous THIRD-PARTY regeneration was applied from the GitHub Actions log, which strips carriage returns. bincode 1.3.3's LICENSE.md has CRLF line endings and `cargo about` embeds it verbatim, so the committed block differed from the generated one by invisible `\r` characters only. Restored `\r\n` endings on the 21-line bincode MIT license block (lines 11299-11319). Verified byte-for-byte: `git hash-object THIRD-PARTY` now yields d579781, the exact blob CI's regeneration step produces. Co-authored-by: Infinity 🤖 <infinity@hydro.run>
Previously, switching the default model via /model (or the modal picker) only updated the in-memory Runtime::default_model, so the selection was lost on restart. - config.rs: Config now records the path it was loaded from (#[serde(skip)] field, None for in-memory test configs). New Config::save_default_model() patches only the default_model field in the JSON file via serde_json::Value, preserving all other fields — including ones unknown to the Config struct. No-op when there is no backing file. - flow.rs: handle_model_command() calls save_default_model() after updating the in-memory default. On save failure it logs the error and appends a warning to the Slack reply that the selection will reset on restart. - runtime.rs: test config literal updated for the new path field. Added unit tests covering field-preserving patching and the no-path no-op. Verified with cargo test and clippy on infinity-slack-bot. Co-authored-by: Infinity 🤖 <infinity@hydro.run>
akainth015
force-pushed
the
push-ssyrmkszpxwn
branch
from
July 31, 2026 23:55
be4648c to
f651237
Compare
…one change Cut the daemon race-condition fix out of PR #43, per request. The fix is superseded by the standalone change `uuokousl` ("fix lost-input race on non-keep-alive session idle exit"), whose diff is essentially identical to the daemon portion of `wltlpvwo` in the PR stack. Created commit `twkklplz` on top of `lptsrxyl` (`l`) that: * Reverts `crates/infinity-daemon/src/session/mod.rs` (worker_shutdown token + send_input retry loop) and `crates/infinity-daemon/tests/keep_alive.rs` (the `input_after_idle_exit_restarts_session` test) to their pre-`wltlpvwo` state — verified byte-identical via `jj diff --from wltlpvwo- --to @`. * Keeps `crates/infinity-slack-bot/src/daemon_client.rs` sending `keeps_session_alive: false` (the protocol field exists on main, so this stands alone). Verified: no commit between `wltlpvwo` and `lptsrxyl` touches the reverted files; `cargo check --all-targets` passes for both `infinity-daemon` and `infinity-slack-bot`; the two remaining keep-alive tests pass. Co-authored-by: Infinity 🤖 <infinity@hydro.run>
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.
This is the Slack bot for Infinity agents. I've been using it for about a month, and love it.
❤️ Made with Hydro