Skip to content

fix(host-service): classify a vanished local address as the cloud being unreachable - #8942

Merged
superset-home merged 1 commit into
mainfrom
sentry/host-service-a2-eaddrnotavail
Oct 27, 2026
Merged

superset-home merged 1 commit into
mainfrom
sentry/host-service-a2-eaddrnotavail

Conversation

@superset-home

@superset-home superset-home commented Oct 27, 2026 •

Copy link
Copy Markdown
Contributor

Problem

host.info reports a 500 to Sentry when the TLS socket to the cloud API fails a read with EADDRNOTAVAIL. In the last 30 days this occurred about 32 times on about 20 different machines, in every release from 1.29 to 1.39. It is spread across the fetch failed issues for host.info (mostly HOST-SERVICE-1B, also HOST-SERVICE-7Y and HOST-SERVICE-A2).

Root cause

rethrowCloudUnreachable already maps transport failures to SERVICE_UNAVAILABLE, so the Sentry middleware does not report the user's network as a host-service bug (#6735, #7680, #8725). EADDRNOTAVAIL is not in UNREACHABLE_CODES. On a read of an established socket, it means the local address the socket used is no longer valid: the network interface changed or lost its address. host-service does not bind outbound sockets to a local address, so this code describes the host's network, not our software.

Fix

Add EADDRNOTAVAIL to UNREACHABLE_CODES, and add a row to the existing table test. This is the same shape as #7680 (ENOTCONN). The existing negative tests are not changed: an error with no code, an HTML body, an expired certificate, and application errors from the cloud API still report.

Verification

New test row fails before the change:

(fail) rethrowCloudUnreachable > EADDRNOTAVAIL is the cloud being unreachable
 25 pass
 1 fail

After:

$ bunx biome check packages/host-service/src/trpc/router/host/cloud-api-error.ts packages/host-service/src/trpc/router/host/cloud-api-error.test.ts
Checked 2 files in 73ms. No fixes applied.

$ cd packages/host-service && bun run typecheck
exit 0

$ bun test src/trpc/router/host
 31 pass
 0 fail
 50 expect() calls
Ran 31 tests across 2 files.

Refs HOST-SERVICE-A2


Summary by cubic

Marks EADDRNOTAVAIL as the cloud being unreachable so host.info no longer reports a 500 to Sentry when a TLS read fails because the local network interface lost its address (Wi-Fi change, sleep/wake, VPN toggle). The error is now mapped to SERVICE_UNAVAILABLE like other transport failures, with a new table test row covering it.

Written for commit 751d598. Summary will update on new commits.

View guided diff Turn on auto-fix

Summary by CodeRabbit

  • Bug Fixes
    • Cloud connection errors caused by unavailable network addresses are now reported as service unavailable, with the underlying error message preserved.

…ng unreachable

A TLS read to the cloud API fails with EADDRNOTAVAIL when the interface the
socket was bound to loses its address (Wi-Fi change, sleep/wake, VPN toggle).
That is the host's network, not host-service, but the code was missing from
UNREACHABLE_CODES, so host.info reported it as a 500.

Refs HOST-SERVICE-A2
@coderabbitai

coderabbitai Bot commented Oct 27, 2026 •

Copy link
Copy Markdown
Contributor

Review in Change Stack →

No actionable comments were generated in the recent review.

Recent review info
Run configuration
  • Source mode: Auto
  • Repository: Unavailable
  • Branch: Unavailable
  • File: No configuration file from this repository applied
  • Commit: Unavailable
  • Layers: defaults
  • Review profile: CHILL
  • Plan: Advanced
  • Run ID: c0728da6-5cd0-43f0-9344-a0911adde5c0
Commits

Reviewing files that changed from the base of the PR and between 5215788 and 751d598.

Files selected for processing: 2
  • packages/host-service/src/trpc/router/host/cloud-api-error.test.ts
  • packages/host-service/src/trpc/router/host/cloud-api-error.ts

Included review availability: This review used your included allowance. Your plan provides up to 10 included reviews per hour; 9 remain after this review.

Walkthrough

The cloud-unreachable error code set now includes EADDRNOTAVAIL. A test checks that this error produces SERVICE_UNAVAILABLE and preserves its underlying message.

Changes: Cloud error classification

Layer / File(s) Summary
Add EADDRNOTAVAIL to unreachable codes
packages/host-service/src/trpc/router/host/cloud-api-error.ts, packages/host-service/src/trpc/router/host/cloud-api-error.test.ts
UNREACHABLE_CODES includes EADDRNOTAVAIL. The test cases check its classification as SERVICE_UNAVAILABLE and preservation of the underlying message.

Priority: Normal

Estimated code review effort: 1 (Trivial) | ~4 minutes

Change: Bug fix

Suggested reviewers: saddlepaddle

Merge Risk: Minimal · up to 751d5

The error classification change is narrowly scoped and covered by the added test.

Pre-merge checks: Passed 5
Check name Status Explanation
Title check Passed The title follows conventional commit format and names the primary change: classifying EADDRNOTAVAIL (a vanished local address) as the cloud being unreachable in host-service. It matches the single fu…
Description check Passed The description covers the problem, root cause, fix, and verification. It follows the template's What & why and How I tested it sections, with concrete before/after test output and a Sentry issue refe…
Docstring Coverage Passed No eligible functions found in the changed files. Skipping docstring coverage check. Docstring coverage is scoped to functions touched by this diff. Documentation-only edits and deletions within survi…
Linked Issues check Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check Passed Check skipped because no linked issues were found for this pull request.
Full details: Title check

Explanation

The title follows conventional commit format and names the primary change: classifying EADDRNOTAVAIL (a vanished local address) as the cloud being unreachable in host-service. It matches the single functional change in cloud-api-error.ts (UNREACHABLE_CODES gains EADDRNOTAVAIL) and the test row added for it.

Full details: Description check

Explanation

The description covers the problem, root cause, fix, and verification. It follows the template's What & why and How I tested it sections, with concrete before/after test output and a Sentry issue reference. The template's Checklist section is absent: it does not confirm the conventional-commit title check, lint/typecheck status in the checklist form, maintainer edit access, or CLA signature. The 'Problem' and 'Root cause' content serves the 'What & why' section, but no explicit issue link using 'fixes #' appears, and no screenshot is needed since the change is not user-visible.

Resolution

Optionally add the Checklist section from the repository template, with each item checked or confirmed. Optionally add an explicit issue link, such as 'Refs HOST-SERVICE-A2' expanded to a linked issue, if one exists.

Full details: Docstring Coverage

Explanation

No eligible functions found in the changed files. Skipping docstring coverage check. Docstring coverage is scoped to functions touched by this diff. Documentation-only edits and deletions within surviving functions are included; unchanged diff context and fully deleted functions are excluded. Analyzed 0 functions across 2 files.

Finishing Touches
📝 Generate docstrings
  • Commit to this branch
  • Create a new PR
🧪 Generate unit tests (beta)
  • Commit to this branch
  • Create a new PR
  • Autofix · Keep fixing CodeRabbit findings and required CI, and resolving merge conflicts

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@greptile-apps

greptile-apps Bot commented Oct 27, 2026 •

Copy link
Copy Markdown
Contributor

RetriggerView in GreptileConfidence Score: 5/5

[Medium impact] The PR appears safe to merge.

Summary

Adds EADDRNOTAVAIL to the cloud network-error list so a vanished local address returns SERVICE_UNAVAILABLE instead of an unexpected server error.

  • Host calls treat a vanished local address as cloud unavailability.

Reviews (1) · Last reviewed commit: "fix(host-service): classify a vanished l..." · Reviewed by Greptile

@github-actions

github-actions Bot commented Oct 27, 2026 •

Copy link
Copy Markdown
Contributor

🧹 Preview Cleanup Complete

The following preview resources have been cleaned up:

  • ✅ Neon database branch

Thank you for your contribution! 🎉

@superset-home
superset-home merged commit 615c02b into main Oct 27, 2026
22 checks passed
NicolaPanero added a commit to NicolaPanero/Paradev that referenced this pull request Nov 2, 2026
* chore(desktop): bump version to 2.0.0 (host-service 1.40.0 -> 2.0.0, cli 1.40.0 -> 2.0.0) (#8897)

* fix(cloud): register pdev independently of the launch agent (#8890)

* fix(cloud): register pdev independently of the launch agent

* fix(cloud): keep pdev trust repair from blocking sandbox boot

* test(cloud): isolate sandbox self-seed pdev trust writes

---------

Co-authored-by: Harshith Mullapudi <harshithmullapudi@gmail.com>

* fix(sandbox): match release repositories without case sensitivity (#8905)

Co-authored-by: Harshith Mullapudi <harshithmullapudi@gmail.com>

* fix(cli): make mcp and skills commands public (#8894)

* fix(cli): make mcp and skills commands public

Plugins are public, but the mcp and skills groups were still internal,
so a normal install fails with "Unknown command". The integrations and
plugins skills tell agents to run both. List them in the CLI reference,
as the docs rules require for public commands.

Fixes #8893

* docs(cli): mark --plugin required on mcp commands

Use the plugin name, not the display name on the Plugins page, and say
when --account is needed.

* fix(desktop,trpc,cli): stop the Google Calendar Connect loop and make plugins public (#8705)

* fix(desktop,trpc): stop the Google Calendar Connect loop

plugins.list hid a connector's connections from an available plugin when any
installed plugin claimed it, so Gmail made Google Calendar look unconnected.
Only third-party installs claim a connector now.

The deep link handler passed the query string inside `href`/`to`, which the
router drops, so ?connected= and ?error= never reached the plugin page. Parse
the path and search before navigating.

The Connect dialog now closes once the plugin is installed, so re-authorizing
an existing account no longer leaves it open.

* fix(desktop): satisfy typecheck and biome on deep link navigate

* fix(desktop): satisfy typecheck on deep link navigate

* fix(desktop): close the Connect dialog on the connected outcome

Closing on installed state missed the case where the plugin is already
installed and the user re-authorizes an account. Found by driving the
deep link in the running app.

* feat(cli): make the plugins command public

Drop the internal audience from `superset plugins` so it shows in help
without SUPERSET_CLI_AUDIENCE=internal, and add it to the CLI reference
as apps/docs/AGENTS.md requires.

* feat(trpc): add user name and email to cloud usage events (#8911)

Autumn's usage chart grouped by properties.userId showed raw UUIDs,
since Autumn has no way to resolve a property value to a person. Send
userName and userEmail next to userId on every metered event so the
chart can group by a readable value. userId stays for joins.

Co-authored-by: Harshith Mullapudi <harshithmullapudi@gmail.com>

* feat(config): .superset/config.toml, dark-launched behind a flag (#8795)

* feat(shared): add project-config module for .superset/config.toml

Schema, parser with line-numbered errors, key-by-key layer merge for
.cloud/.local tables and the personal file, and v1 config.json
translation. Uses smol-toml. Includes the format design and the
implementation plan.

* feat(host-service): read .superset/config.toml for setup, archive and Run

config.toml (workspace checkout, else main checkout) plus the personal
config.local.toml from the main checkout now drive setup (install then
setup), archive, the Run button (first default terminal) and the cloud
boot hook. v1 config.json keeps working unchanged. Workspace creation and
archive report workspace_config_resolved with the format and the v1
features still in use.

* feat(cloud): run [archive] when a cloud workspace is archived

The reap job's stop stage now calls the new host-service procedure
sandbox.runArchiveHook on the still-running box before stopping it, so a
repository can release what setup created outside the box (this repo's
per-workspace Neon branches leaked otherwise). Best effort: failures and
older boxes still stop. An undo during the hook re-runs setup through
runStartHook's new optional force input instead of stopping the box.
The cloud reader also reads ports from config.toml.

* feat(desktop): edit the whole workspace config in Settings

Settings → project → Workspace config replaces the three-tab scripts
editor: a text editor for the shared .superset/config.toml and the
personal config.local.toml, validated as you type, saved explicitly so
comments survive. Projects on config.json can keep editing it or convert.
New host-service procedures config.getConfigFiles and
config.writeConfigFile; the old ones stay for released builds. An older
host shows an update notice.

* feat: convert this repo to config.toml and update docs and prompts

Docs describe .superset/config.toml (cloud-only parts stay out while cloud
workspaces are flagged). Desktop setup prompts ask agents for config.toml.
This repo gets config.toml plus a Neon-only cloud archive script; its
config.json stays until sandboxes run a host-service that reads TOML.
Saving the personal file adds it to .git/info/exclude. Translations for
every new string.

* fix(host-service): only the sandbox's own host-service resolves config for the cloud

A local app launched from a sandbox terminal inherits IS_SANDBOX=1, so
checking it resolved local workspaces with the cloud tables. Found by
calling the running dev host-service. Adds integration tests for
config.toml [archive] and the personal file through a real terminal.

* docs(plans): record config.toml v2 outcomes and follow-ups

* fix(desktop): settings search finds the Workspace config section

* docs: show the Workspace config editor and how to check setup works

* feat(dev): lease cloud workspace Neon branches for 7 days

setup.cloud.sh creates the branch with a 7-day expiry and renews it on
every boot, so an archived or abandoned cloud workspace releases its branch
through Neon's own expiry even when no archive hook runs. Local workspaces
are unchanged. The mismatch entry now says cloud [archive] is best effort.

* fix(cloud): cap cloud [archive] at the same 60 seconds as a local archive

One key, one limit: the box uses TEARDOWN_TIMEOUT_MS, and the API waits
90 seconds. Docs note that archive commands are stopped after 60 seconds
and can run more than once.

* fix(config): address review findings on config.toml

- setup.cloud.sh renews the Neon lease on every boot, also when the
  workspace is already set up. Before, only the first boot set it, so a
  live workspace's branch expired after 7 days. If the branch is gone
  ([archive] deleted it, or it expired), setup runs again instead of
  keeping a .env for a deleted database. That makes Undo after archive work.
- Cloud boot from config.toml stops [setup.cloud] at its first failed
  command, like a local [setup]. The default terminal still starts after.
- The cloud archive hook runs only for config.toml projects. A v1
  config.json teardown is local only and cloud boxes never ran it.
- The archive hook kills the whole process group on timeout, and reports
  the timeout even if a process outside the group holds the pipes.
- Converting a v1 file keeps the Run terminal out of cloud boot
  ([terminals.dev.cloud] default = false), as v1 cloud boot ran only start.
- Workspace config editor: tab switches keep unsaved edits, typing after
  Save survives the refresh, and Convert uses the unsaved draft.
- Plans and a settings-search description corrected; de and es wording.

* feat(config): put config.toml behind the workspace-config-toml flag

The format needs testing before people are led to it.

- Settings shows the config.toml editor only with the PostHog flag
  `workspace-config-toml`. Off shows the config.json form from main.
- The docs page, the new-workspace sample prompts, the setup card prompt
  and the settings search entry are back to main's config.json versions.
- The config.toml docs page and its images move to apps/docs/drafts/,
  which is not published, for launch.
- host-service still reads a config.toml wherever one exists: a project
  opts in by having one, and a v1 project runs as before.
- The plan records the rollout (canary, beta, launch) and how config.json
  is deprecated.

* refactor(config): read v1 through its conversion, so config.toml is the only model

host-service used to map a config.toml back onto v1's keys (resolveScript)
and then split them apart again for cloud boot. Now v1 goes through
fromV1Json and every caller reads the resolved sections:

- loadProjectConfig returns the config.toml model for either format;
  chainPhases builds a section's command. resolveScript, ResolvedScript,
  resolveTomlScript and combinePhases are gone.
- A .superset/<key>.sh fallback is a `bash '<path>'` command, so the
  teardown terminal has one path (buildTeardownInitialCommand removed).
- The API reads v1 ports through the same conversion; RepositoryHooks is
  just the ports the claim uses.
- fromV1Json spells out v1's cloud behavior: [setup.cloud] is start or
  nothing, [archive.cloud] is off. A v1 file and its conversion behave the
  same, which also lets the cloud archive hook drop its v1 special case.
  99 public config.json files keep their local and cloud behavior.

In a cloud workspace a v1 project loses its Run button, and a multi-entry
v1 start stops at its first failure. Cloud workspaces are internal.

* feat(channels): in-app channels behind the channels flag (#8864)

* feat(channels): in-app channels behind the channels flag

Org members can create public or private channels, post with markdown and
@-mentions, reply in threads, edit and delete messages, and link a channel
to a task project. The desktop shows joined channels in the sidebar with
unread and mention counts, a channel view with Messages and Tasks tabs, and
a right panel that opens threads and existing task details.

- db: channels, channel_members, channel_messages (migration 0165). A
  per-channel seq, taken under the channel row lock, orders messages.
- api: channel and channelMessage routers. Private channels read as not
  found to non-members; channel admins (and org owners/admins) manage.
- realtime: a per-user UserHub durable object. The API decides recipients
  and publishes after commit; delivery is best effort, and clients refetch
  on reconnect and focus.
- desktop: native notifications for mentions and replies in threads you
  follow, suppressed while that channel is on screen.
- chat-ui: PromptInput takes allowAttachments so channels can turn off
  files, which v1 does not store.

* feat(channels): read state, threads, channel tasks and review fixes

Read and unread follow Slack's client: the server keeps a per-member read
point; the client marks read only when the first unread message is on
screen in a focused window, keeps the "New" line where it was when the
channel opened, starts where the reader left off, and shows an unread bar
with Jump. Esc marks a channel read, Shift+Esc marks everything read after
a confirm, and Option+click marks a message unread. Channels can be muted.

Threads: replying, being mentioned or writing the root follows a thread;
followed threads have their own read point, a Threads row and view.

Tasks belong to a channel through channel_tasks instead of a linked
project; moves are recorded in the task history and the task page has a
Channel field.

Messages gain reactions, files and image previews sized like Slack's; the
composer gains code blocks, :emoji:, Slack formatting keys and an edit
variant. Presence reports only when the channels flag is on.

Migration 0165 replaces the branch's earlier migrations (squashed with
drizzle-kit drop and generate). Two task_activity statements were moved
after the channel_tasks foreign key so the migration locks tasks before
task_activity, the same order task updates use; the end state is what
drizzle-kit generated.

* fix(auth): a failed desktop sign-in no longer lands on the web dashboard (#8899)

* fix(auth): finish a failed desktop sign-in on the desktop page, keep ?redirect for signed-in visits

A desktop sign-in whose provider callback failed (for example the
state_mismatch from finishing in an older tab after "Open browser again")
fell back to the global errorURL, /sign-in?error=…. In a browser that was
already signed in on the web, the proxy then sent it to /, so the user
landed on the dashboard with no error and the desktop got no token.

- Desktop connect passes errorCallbackURL=/auth/desktop/success, which
  shows "Sign-in didn't finish" (no desktop_state, so no token is minted).
- A signed-in visit to /sign-in or /sign-up follows a same-origin
  ?redirect= path instead of always going to /. Other origins, //host,
  /\host and auth pages are refused.

* fix(web): check the resolved redirect origin, not the raw string

URL parsing drops tab and newline characters, so `/\t/evil.example`
passed the "starts with one slash" check and resolved to
https://evil.example. Resolve the redirect against the request origin
and keep it only when the origin is unchanged.

* fix(setup): don't expand an empty lease_args array under set -u (#8920)

macOS bash 3.2 treats "${arr[@]}" on an empty array as an unbound
variable under set -u, so Neon branch creation failed whenever
NEON_BRANCH_LEASE_DAYS was unset (regressed in #8795).

* fix(cloud): make cloud chats usable while sandboxes start or wake (#8909)

* fix(cloud): resume chats safely when waking sandboxes

Verify agent liveness on each host connection before sending queued prompts, and show the resume notice while readiness is unknown. Preserve never-sent text when recovery replaces a session and let pending prompt batches drain after later reconnects.

Wait for the sandbox managed environment before chat and boot-resume launches, and retry failed wakes sooner without changing successful keepalives. Document the required host bundle release and the limits of local verification.

* fix(ci): translate chat fixture and sequence sandbox boot checks

The i18n ratchet includes fixture helpers, so render the existing resume message through Trans and mirror the macro shim used by the desktop preload.

The boot runner starts the repository hook only after checkout completes. Wait for the existing checkout-ready assertion before checking the hook so a slow clone does not consume the hook's separate budget. Keep both assertions and their timeouts unchanged.

* feat(desktop): show the cloud chat pane while the agent starts

A new cloud workspace with an initial agent prompt used to show no chat
pane until the sandbox had checked out the repo, received its environment
and created the chat session. The create API now reports the planned
launch (agent, surface, prompt hash), and the desktop seeds a waiting
chat pane that the host session replaces in place. The draft, queued
messages, and model/mode carry over, and the server-sent initial prompt
is never resent.

- Attach only to the session whose first `superset-sessions` message
  matches the launch's prompt hash; collaborators' chats get their own tab.
- Closing the waiting pane suppresses adoption of that launch.
- After three minutes with no match, attach the single launched chat for
  that agent if there is one, else release the pane as a normal new chat.
- Remove waiting panes when agent chat is disabled.
- Keep the waiting pane in the center when the right pane area is on.
- Report chat launches with a saved effort as chat, as the sandbox does.
- Keep `draftId` across agent switch and rewind.
- Keep the missing-worktree screen up during a refresh.
- Show the provisioning screen until the box is ready.

* fix(desktop): stop holding verified chats on remount and bound launch polling

Review follow-ups on the cloud chat changes:

- Chat liveness holds sends only when there is no successful check for
  the current connection. A remount on the same connection, such as a tab
  switch, keeps the verified chat usable and rechecks in the background.
  A reconnect or new host address still holds until a fresh check.
- Polling for chats without a first message backs off from 2 s to 60 s
  instead of polling every 2 s without a bound.
- Keep first-prompt hashes in a per-hook map pruned to listed sessions
  instead of the query cache, which collected them after five minutes.

* fix(desktop): release the waiting chat on a terminal launch and skip its resume flash

More review follow-ups on the cloud chat waiting pane:

- When the box runs the launch's agent in a terminal (a live terminal
  agent binding without a chat session), stop waiting at once. Close the
  pane if nothing was typed, since the terminal gets its own tab; else
  release it as a normal chat that keeps the draft and queued prompts.
- When a waiting pane attaches the box's session, record it as live for
  the current connection, as the session list just reported. The new chat
  pane no longer shows "Resuming the conversation…" while it rechecks.

* test(desktop): seed the waiting-pane draft through the composer draft writer

The fixture called localStorage.setItem directly, which the persisted-key
registry check counts as an unregistered writer.

---------

Co-authored-by: Harshith Mullapudi <harshithmullapudi@gmail.com>

* fix(web): let shared workspace and plugin links reach the desktop app signed out (#8923)

/workspaces/<id> and /plugins(/<name>) only redirect into the desktop app,
like /tasks and /automations, but were missing from the public routes. A
signed-out browser hit the sign-in wall and never opened the app.

Co-authored-by: Claude <noreply@anthropic.com>

* feat(channels): Slack-style notifications for channels (#8921)

* fix(desktop): mention chips use the right people on every channel message

Streamdown caches its unified processor by plugin name plus options. The
mention plugin was an anonymous closure, so every message reused the first
message's people and most chips rendered as plain text. Pass the people as
plugin options instead.

* feat(trpc,db): Slack's notification rules for channels

Port Slack desktop's shouldShowNotificationForChannelMessage order into one
rule function: level from the channel or the person's default, a direct
mention or keyword gets through a mute, @channel/@here/@everyone with a
per-channel suppress option, @channel ignored in replies, Do Not Disturb
(pause and schedule) blocks everything, and only members are notified.

Migration 0166 adds channel_notification_prefs, makes channel_members.notify
nullable (null follows the default), adds suppress_broadcasts and
channel_messages.broadcast. Mention counts include broadcasts.

* feat(desktop): Slack-style channel notifications

- Slack's wording ("New message in #ch", "Name: text", preview off gives
  "a message from Name"), silent banner plus the app ringtone with a 5 s
  debounce per conversation, dock bounce from the person's setting.
- No banner for the conversation on screen: the channel, the thread open in
  its panel, or the Threads view. @here is dropped while away.
- Clicking jumps to the message or opens the thread; reading a channel or
  thread closes its banners.
- Dock badge adds channel and thread mentions, with a dot for other unreads.
- @channel and @here in the @ menu, rendered as chips.
- "Not in this channel" prompt with Add them / Do nothing after mentioning
  a non-member, since only members are notified.
- Per-channel menu gains Default and Ignore @channel and @here; Settings >
  Notifications gains a Channels section; account menu gains Pause
  notifications.

* chore(i18n): translate channel notification strings

* feat(desktop): drop the mention count from channel and Threads rows

Unread rows stay bold; how many were missed is not worth a red badge.

* fix(desktop): dev builds show native notifications

patch-dev-protocol edits the dev Electron.app's Info.plist, which breaks its
signature, and macOS then drops every notification the app shows without an
error. Re-sign the bundle ad hoc after patching, and re-patch when the
signature is invalid.

* feat(channels): render notifications the way Slack's banners look

Measured against the Slack banners on a Mac signed in to several
workspaces (Notification Center records): the workspace is the title, the
channel the subtitle, and the body is "Name: text" as plain text with line
breaks kept. Ours showed raw markdown, link targets and one flattened line
cut at 140 characters.

- notificationTextOf drops formatting marks and link targets, keeps line
  breaks, caps at 1,000 characters (replaces previewOf)
- channel.notify carries organizationName; title is the organization,
  subtitle #channel or Thread in #channel

* feat(desktop): Reply from the banner and per-conversation stacks, like Slack

Read from Slack desktop's main bundle (app.asar): it creates an Electron
Notification with id, groupId, groupTitle and hasReply, and a banner reply
goes to the thread it came from, else the channel.

- showNative takes id, groupId and replyTarget; macOS stacks a
  conversation's banners and offers Reply; the typed text comes back over
  the notifications subscription and is sent as a message
- closeGroup uses Notification.removeGroup, so delivered banners go even
  after a restart
- notification text cap 2,000 characters, as Slack's body

* fix(desktop): agent hook listener ignores notification replies

* feat(channels): shared @ menu with tasks and plugins, and a # menu for channels (#8924)

* feat(channels): shared @ menu with tasks and plugins, and a # menu for channels

* fix(channels): line up @ menu rows on one 20px icon slot, with a megaphone for @here and @channel

* fix(api): reject a malformed plugin connection id with a 400 (#8926)

* fix(api): reject a malformed plugin connection id with a 400

A connection query parameter that is not a UUID reached the connections
query, Postgres refused the cast, and the request failed with a 500 that
told the caller nothing. Validate it where the request enters.

Refs API-3S

* fix(api): keep an empty connection parameter meaning no pin

---------

Co-authored-by: superset-home <338338640+superset-home@users.noreply.github.com>

* fix(desktop): stop leaked chat sessions from reopening as new tabs (#8919)

* fix(desktop): stop leaked chat sessions from reopening as new tabs

A chat whose host session stopped (for example after an update restarts
the host service) recovers by creating a replacement session, and takes
it over only once the agent finishes starting. Only the active tab is
mounted, so switching away in that window dropped the replacement while
it stayed live on the host under the pane's terminal id. Each revisit
leaked another one. Closing the tab released them, and the next session
change (such as "+" -> Chat) adopted each one as a new tab that showed
"No earlier conversation to open, so this is a new one."

- Close an uncommitted replacement when the chat pane unmounts.
- When a chat closes, skip its terminal in auto-adopt and stop the live
  sessions on that terminal that no pane holds. This also clears leaks
  that hosts already have.

* fix(desktop): keep closed chats recoverable when a stop fails or Undo is pending

Review follow-ups on the leaked chat session fix:

- When stopping a closed chat fails, also clear its terminal mark, so
  auto-adopt can show the chat that is still running.
- The stray-session sweep on close now keeps every session that is still
  in its Undo window. Before, closing a second tab on the same terminal
  could stop the first tab's session before Undo expired.
- Reopening a chat from history or an automation link clears its
  terminal mark, through a new reopenChatTerminal helper.

* feat(chat): one failure card per failed turn, with ways to recover (#8933)

* feat(chat): name why an ACP turn failed

A failed turn's error now carries a kind (crashed, timeout, auth, network,
model_unavailable, context_too_long, refused, session_lost, unknown), the
stderr the agent printed during that turn, and, for an auth failure, the
login command for the profile the agent launched with. All fields are
optional, and older clients still read `message`.

The message is the agent's own words without the JSON-RPC method in front.
A crash fails the turn once: the rejected prompt no longer fails it a
second time after the exit already did.

The continue instruction is shared, so the failure card and the resume
after an update send the same words.

* feat(chat): a chat that replaces a dead one keeps the failure that ended it

When the agent dies, the pane opens a replacement chat that resumes the
agent's session, and the transcript is the agent's replay. The failure
that killed the old chat was only in the old journal, so it vanished.

createSession now takes `resume.replacesSessionId`. When that chat's last
turn failed, the new chat writes the turn after the agent has started, so
it follows the replayed history. Older hosts ignore the field.

* feat(desktop): one failure card on a failed chat turn, with ways to recover

A failed turn showed the agent's error twice in red: as a notice and again
as "Turn failed: ...". It now shows one card on the turn: a title for the
kind of failure ("Task interrupted" when unknown), the agent's words and
the stderr it printed clamped to three lines with Show details, Copy
error, and a dismiss that leaves a one-line record.

The chat's last failed turn also offers what fits the failure: Continue
sends the shared continue prompt; Change model opens the composer's model
picker and continues once a model is picked; Sign in shows the agent's
login command and Continue then restarts the agent so the sign-in takes;
Start new chat opens a fresh chat when this one cannot go on. Actions
are off while a turn runs, a prompt is on its way, or the chat resumes.

The card is built from the stored turn, so it reads the same when the
chat is opened again, and a chat that replaces a dead one is told which
chat it replaces so the failure carries over. A turn the user stopped
stays one quiet "Stopped" line.

* fix(chat): a carried failure follows the replay it comes after

The agent stamps replayed history ahead of the clock to keep its order,
so a failure carried into the replacement chat at the wall-clock time
sorted above the replayed messages and no longer read as the last turn,
which hid its recovery actions. It is now stamped after the newest start
the chat has written.

* fix(desktop): restart once for sign-in, and keep Change model on this agent

Continue after a sign-in failure restarted the agent twice. The pane
wrote the new chat and the queued continue prompt as two data updates in
one tick; both spread the last rendered data, so the second put the
stopped chat's id back and recovery replaced it again. The continue
prompt now rides on the new chat's own update, and the restart goes
through the pane's usual recovery instead of starting one of its own.

Change model opens the model picker on the current agent's models only:
picking another agent there would fork the chat, which is not what
recovering a failed turn asks for.

* i18n: translate the failure card in every locale

The card's titles and actions in all 16 locales. "Start a new chat"
reuses the recovery screen's existing message, and the three strings
the card replaced ("Interrupted", "Turn failed", "Turn failed:
{message}") leave the catalogs.

* chore(desktop): drop comments the failure card's code already says

* fix(chat): continue after Change model only once the agent took the model

Review follow-ups:

- setConfigOption takes an optional `confirm`. With it the host waits for
  the agent's answer and fails when the agent refused the setting; without
  it nothing changes, and older hosts drop the field. Change model sends it,
  so a refused model no longer continues the task on the old one.
- A failed turn saved by an older host kept the crash's stderr only in the
  error notice the card hides. The card now takes what that notice adds past
  the turn's message as its detail when the turn has none.

* fix(chat): address review on the failure card

- A carried failure no longer ties the next turn: the agent's clock can
  give the turn after a long replay the carried failure's start, and the
  tie sorted at random, so Continue could land above the card. Turns that
  start at or before the carried failure move just after it.
- A setting that timed out is not confirmed, so Change model does not
  continue on a model the agent never acknowledged.
- The card shows the stderr printed during the failed turn: a steered v2
  turn resets it, and a crash no longer shows output from startup or
  earlier turns. The old-host notice fallback applies only to turns
  without a kind.
- Auth and network rules no longer match a bare "credentials", "/login"
  or "network" in unrelated errors.
- Restarting for sign-in reports a failed stop (toast, and the button
  returns), and the pending continue is dropped when the chat continued
  in a terminal instead of recovering.
- The clamp is measured again when a dismissed card is restored.
- es/it say "crashed", vi and zh-CN use the catalog's term for agent.

* fix(chat): keep every turn after a carried failure in order

Review follow-ups:

- Moving only the first turn past the carried failure left the next one
  free to land on the same start after a long replay. Each new turn now
  starts after everything the chat has written, decided once per turn so
  its later events keep the same start.
- The pending continue for a sign-in restart is dropped in an effect, not
  during render.

* fix(host-service): classify a vanished local address as the cloud being unreachable (#8942)

A TLS read to the cloud API fails with EADDRNOTAVAIL when the interface the
socket was bound to loses its address (Wi-Fi change, sleep/wake, VPN toggle).
That is the host's network, not host-service, but the code was missing from
UNREACHABLE_CODES, so host.info reported it as a 500.

Refs HOST-SERVICE-A2

Co-authored-by: superset-home <338338640+superset-home@users.noreply.github.com>

* feat(chat): app slash commands in the chat composer (#8928)

* feat(chat-ui): rank slash commands by alias and description, complete with Tab

- The / menu matches a command's name, its aliases and the words of its
  description, with a loose letters-in-order match on names last. Groups
  stay together, led by the group with the best match; the first group
  wins a tie.
- Typing a space ends the menu, so Enter sends the command with its
  arguments instead of picking a row.
- A command can carry a completion chip. Tab inserts it in place of the
  typed name instead of running the command.

* refactor(desktop): move automation creation into a shared hook

useCreateAutomation holds what the automations page did inline: pick the
agent, write the automation, open its editor, all behind the paywall. The
page calls it for templates and for "New automation"; the chat composer
will call it with a prompt.

* feat(chat): app commands in the composer's / menu

The ACP chat composer's / menu now leads with the app's own commands, under
the product name, before the agent's Commands and MCP Server Commands.

Commands, each shown only when the chat can carry it out:
- /new (clear, reset): a new chat with the same agent, in a new tab.
- /model: opens the model picker.
- /fast [on|off]: flips or sets the agent's Fast option.
- /plan and /default: enter and leave plan, as a session mode (Claude) or
  as a plan config option (Codex). Leaving goes back to the mode plan was
  entered from.
- /fork here, /fork worktree (branch): Branch this conversation.
- /resume (continue) and /mcp: as before.
- /find: opens find in the transcript.
- /attach: opens the file picker.
- /automation [prompt]: creates an automation with that prompt for this
  chat's agent and project, and opens its editor.

One registry holds each command's name, aliases, icon, description,
argument hint, availability and action; /mcp and /resume moved into it.
A small table decides a name both sides offer: the app keeps clear, fork,
resume, plan, mcp and the rest it does better, the agent keeps compact and
review, and any name not listed stays the agent's.

Typed commands run on Enter and clear the composer. A bad argument or an
attachment shows a toast and keeps the draft. An app command is never sent
to the agent.

Plan is not saved as the mode new chats start in. The cs, fr and id
catalogs had translated the agent's "Plan" as a billing plan.

* fix(chat-ui): letters-in-order matches must start with the command's first letter

"clear" matched /slack:slack-search and other long names whose letters
happen to appear in order. The loose match now needs the same first letter,
so "cmpt" still finds /compact and the noise is gone.

* fix(chat): name the tab /new opens after the agent

The chat's agent label is looked up by config id, which does not match a
preset id, so the new tab fell back to "Chat". Look it up by preset as the
New chat shortcut does, so it reads "Claude".

* fix(chat-ui): search command descriptions only when no name matches

Agents list many skills with long descriptions, so /model and /new pulled
in every skill that mentions the word. Descriptions are now a fallback for
when no name or alias matches: /hist still finds /resume and /worktree
still finds /fork worktree, and /model shows /model.

* style(chat-ui): compact slash menu rows

The / menu ran at 15px with 31px rows, larger than the composer text it
opens from. It now uses 12px text in 26px rows:
- name in medium weight, argument hint after it, description dimmer and
  on the same line, truncated
- 14px icons in a 16px column, brighter on the highlighted row
- 12px group labels with a hairline between groups
- an opaque panel with a border and a soft shadow, at most 288px tall

* fix(chat): keep typed /mcp open and keep /automation's prompt when it fails

- A typed /mcp opened its picker inside the submit that then cleared the
  composer, so the picker closed at once. It opens after the submit now,
  as it did before the command registry.
- /automation cleared the prompt before the paywall or the server had
  answered. If no automation is created (no paid plan, the plan check
  fails, the server refuses it), the typed command goes back into the
  composer. runGatedFeature is gateFeature that also says whether the
  action ran.

* fix(chat): bare /automation asks for a prompt, and plan names never reach the agent

- /automation with nothing after it shows its usage and keeps the draft
  instead of creating an empty automation. Picking it from the menu already
  waited for a prompt.
- /plan and /default are available whenever the agent has plan, and the
  menu lists only the one that applies. Before, the one that did not apply
  dropped out of the name table, so an agent's own /plan came back and a
  typed /plan went to it as text.

* fix(chat-ui): scroll the / and @ menus to the highlighted row

Arrow keys moved the highlight but the list did not follow it, so after
about 11 rows the highlighted command was out of view. Both menus now
scroll the highlighted row into view, as the emoji menu already does.

The first row of a / menu group keeps its group label in view, so
arrowing up into "Commands" or back to the top shows the heading.

* fix(chat): a failed /automation no longer joins a newer draft

When creating an automation failed, the typed command came back through
appendText, which adds it on a new line under any text in the composer.
If the user had started another message while the plan check or the
request ran, the old command joined that message, and Enter sent both
to the agent.

The command now comes back only when the composer is empty: no text,
pasted cards or attachments (new PromptInputHandle.isEmpty). Otherwise a
toast offers to add it back for 15 s, so the new message stays as typed
and the prompt is not lost.

* fix(chat-ui): keep the keyboard highlight when the / and @ menus scroll

A row that scrolled under a resting pointer got mouseenter and took the
highlight, so ArrowDown jumped back to the row under the pointer and
Enter could run that command. Rows now highlight on mousemove, which
only real pointer movement fires.

* feat(desktop): context window ring, limit line and /status in the chat composer (#8929)

* feat(chat): report context window usage from ACP agents while a turn runs

Both ACP agents send usage_update with `used` and `size` (tokens in the
context, window size) during a turn, and Claude adds a cost at the end of
each agent cycle. The adapter kept only the cost, to find the end of a
cycle. It now keeps the latest report and sends it again on the running
turn as `Turn.sessionUsage`, so a client sees the context fill up live and
finds the last value again after a reload.

`sessionUsage` is optional and drops itself when malformed, so released
clients and older hosts are unaffected. It is carried on turn events, not
session state: a session snapshot journals ~24 KB per write, a turn ~150
bytes. Only a running turn is sent again, because a settled turn re-runs
queue delivery in the live session.

At turn end, `Turn.usage` gets the prompt response's token counts plus the
context used and size. `Turn.usage.costUsd` stays empty: the agent reports
a running session total that a resumed session continues from, not a
per-turn cost, so the total rides on `sessionUsage.costUsd`.

`latestSessionUsage` reads the newest report from either shape, so the
native Codex adapter's `usage.contextUsed/contextSize` works too.
`sessions read` prints each turn status once instead of once per update.

SUPER-2733

* feat(desktop): context window ring, limit line and /status in the chat composer

A ring beside the model pill shows how full the agent's context window
is, from the usage the turn reports: neutral below 75%, warning to 90%,
destructive above. It appears with the first report and keeps its size
in narrow panes. Its popover gives the share used, used and window
tokens, what is left, the session cost when the agent reports one, and
Compact now, which sends the agent's own /compact when it offers one.

When the account the chat's agent runs under passes 80% of a quota
window, a quiet line above the composer names the window and when it
resets. It can be dismissed until that window resets.

/status opens a session status sheet: agent, model, effort, Fast, mode,
workspace kind and branch, the context window (or "Not reported yet"),
and the account's rate-limit windows. It is defined like /mcp, as a hook
that returns its command and dialog, so a command registry can take it
whole.

The account quota lookup moves out of AccountUsage into useAccountUsage
so the pane header, the limit line and /status share it.

SUPER-2733

* fix(desktop): name the mode and agent in /status as the composer does

/status showed the agent's own mode name ("Bypass permissions") while the
mode pill says "Full access", and left out the agent when the pane's
agent config has no label. The mode names move out of ModePicker into a
shared map so both read the same words, and the agent falls back to the
name of the agent bound to the terminal.

SUPER-2733

* chore(i18n): translate the context window ring, limit line and /status

SUPER-2733

* fix(desktop): shorten the compaction help in the context popover

"Summarizes the chat to free up context." replaces the longer sentence.

SUPER-2733

* chore(desktop): drop comments the names already carry

SUPER-2733

* fix(chat): run a turn's start and end handling once, not on each report

The live session cleared a pending steer and cancelled again on every
running-turn event, and delivered the next queued prompt on every
settled-turn event. An adapter that reports a turn again, as the ACP
adapter now does with each usage report and the native Codex adapter
does with token updates, could then cancel twice during a steer or send
the next queued prompt while the previous one still waited for its turn,
so prompts landed in the wrong turns. A report that repeats the current
turn's id and status is now journaled without running that handling.

SUPER-2733

* fix(chat): keep session usage current and skip reports that change nothing

- A cost report without context counts kept the old cost; it now updates
  the cost on top of the last counts.
- Usage that arrives between turns, such as the cost of background work
  after a turn settles, now updates the last turn instead of waiting for
  the next one. The live session no longer acts on a repeated turn, so
  settled turns can be sent again safely.
- A report identical to the last one is not sent again, which drops the
  repeats that rate-limit events produced.
- `latestSessionUsage` no longer falls back to `Turn.usage.contextUsed`:
  the native Codex adapter fills it with the thread's running total, not
  what is in the context, so the ring could pass 100%.

SUPER-2733

* fix(desktop): review fixes for the context ring, limit line and /status

- Compact now is disabled while any turn runs, including one held for
  background work, where sending /compact would have steered it into
  that turn.
- /status shows "Usage is out of date" next to cached quota windows
  instead of presenting them as current, drops windows with no valid
  percentage as the pane header does, and leaves Workspace empty for a
  project-less session workspace instead of calling it Local.
- The limit line's dismissal is shared across panes and remounts and
  keyed by account, window, reset and threshold, so it comes back when
  the window resets or usage passes 95%.
- The context window's "Window" label gets its own message context; it
  shared one with the app's Window menu.
- Czech and Polish "tokens left" no longer inflect around the number.

SUPER-2733

* fix: keep an early cost report and let the limit line return after usage drops

- A cost the agent reports before any context usage is kept and shown
  once the counts arrive, instead of being dropped.
- Dismissed limit warnings for an account are forgotten once its usage
  falls back under 80%, so a later climb warns again even for a window
  that reports no reset time.

SUPER-2733

* docs(marketing): show the former name in structured data and llms.txt (#8944)

* docs(marketing): show the former name in structured data and llms.txt

- Organization, SoftwareApplication and WebSite JSON-LD get alternateName "Superset"; Organization sameAs keeps the old GitHub, X, LinkedIn and YouTube profiles.
- docs llms.txt and llms-full.txt open with "Paradev (formerly Superset)".
- About page lead and meta description say "Paradev (formerly Superset)".
- blog/a-new-name: past tense now that the rename shipped; the lead names Paradev, formerly Superset, and what it does; "What Superset does" becomes "What Paradev does".

Co-Authored-By: Claude <noreply@anthropic.com>

* docs(marketing): address review on the former-name PR

- Drop the next dev generated apps/docs/AGENTS.md change.
- a-new-name: say superset.sh pages redirect, since /mcp goes to the API host.
- About meta description: translate "workspace" in id, it, ru, vi; clearer YC wording in fr.

Co-Authored-By: Claude <noreply@anthropic.com>

---------

Co-authored-by: Claude <noreply@anthropic.com>

* feat(desktop): link cards, chat references and composer link chips in chat (#8918)

* fix(chat-ui): show a half-written link as its label while a reply streams

Streamdown's default placeholder href for an unfinished link is blocked by its
own URL filter, so readers saw grey "label [blocked]" until the URL closed.
Use remend's text-only link mode: the label shows as plain text, then the link.

* feat(host-service): add description and preview image to linkPreview.get

- description from og:description, twitter:description, then description
- image only when the caller passes image: true; checked by magic bytes,
  sized from its header, refused over 8192 px a side or 16 MP
- stop reading a page at </head>

Additive: a new optional input and new output fields.

* feat(trpc): add plugins.slackLinkPreview for Slack link hover cards

Reads a Slack message (author and text, finding replies in their thread) or a
profile (name and title) with the caller's own Slack connection, through the
hosted Slack plugin. Anything short of one ready connection, or a link that is
not a Slack message or profile, reads as unavailable without a call.

* feat(desktop): link cards, file link menu, composer link chips and chat references

- website, GitHub and Slack hover cards in assistant and user messages,
  loaded when the pointer arrives; a page open in a browser pane uses a
  screenshot of that tab; banner or thumbnail from the image's shape
- file links: Open in (Tab, New Tab, Editor) and Copy Path; copying keeps
  path:line
- composer: a pasted link becomes a named chip with its app icon and the same
  card; no chip card or tooltip while a composer menu is open
- chat references: a Chats @-mention, a chat chip with a preview card that
  opens the chat, a hidden line telling the agent to read it with
  superset sessions read; the web route forwards sessionId to the deep link

* fix(desktop): only plain ids make a chat reference

A chat link's workspace and session ids went unchecked into the hidden
"superset sessions read" line sent to the agent, so a crafted link such as
?sessionId=x;curl ...|sh could put a shell command in its instructions.
Accept only [A-Za-z0-9_-] ids, and check again before writing the command.

* fix(trpc): read Slack link previews from Slack's API, not tool text

Parsing the Slack tools' display text let a line inside one message pass for
another message, and picked up tool footers. Call conversations.history /
conversations.replies narrowed to the exact ts, and users.info, with the
caller's token from the hosted Slack server. Remote (third-party) servers are
no longer used.

* fix(host-service): keep at most 20 link previews with images in memory

Each can hold a 1 MB image and an icon as base64; page-only previews keep the
200-entry cache.

* fix(desktop): tighten link previews after review

- chat previews check the session belongs to the linked workspace and key
  their cache by host
- the website card shows host and path; links keep their URL tooltip where no
  card can render
- open-tab captures give up after 1.5 s, like the card that waits for them
- move a doc comment back onto BROWSER_ZOOM

* feat(chat-runtime): search session titles in listSessions before the limit

An optional query keeps sessions whose title contains it, case-insensitively,
so the composer's Chats mention can find chats older than the 50 most recent.
Additive: older hosts ignore the field.

* fix(desktop): preview a referenced chat on its own workspace's host

The chat preview read the current workspace's host, so a link to a chat on
another host always showed as unavailable. Resolve the linked workspace's host
and build a transport for it; send the Chats search query to the host.

* fix(host-service): don't end a preview's head at a </head inside a script

* fix(desktop): address superset-home review of chat link rendering

- a chat link to a workspace this machine doesn't know shows unavailable
  instead of loading forever
- Cmd/Ctrl+Shift+V pastes a link as text, not a chip
- Slack cards load only when opened: they share the Slack rate limit with the
  agents' Slack tools
- a chat title reaches the agent without backticks or control characters, and
  at most 80 characters

* fix(trpc): cache Slack link previews per user

Hover cards share Slack's rate limit with the agents' Slack tools. Keep a
found preview 5 minutes and an unavailable one 30 seconds, per user and link,
at most 500 entries.

* fix(desktop): read a chat on another machine through its host; test chat card states

- the hidden line adds --host when a referenced chat's workspace is on a
  different machine than the agent's (superset sessions read uses the agent's
  own machine otherwise)
- chat card state mapping moved to chatPreviewLoad and tested, including the
  not-found workspace that used to load forever

* fix(desktop): show a new cloud workspace's prompt while its chat starts

The new-workspace dialog sends the prompt to the API, and the desktop kept only its hash. The waiting screens ("Starting the agent…", and "Resuming the conversation…" while the box's connection comes up) had nothing to show. Keep the typed text on the pending pane and show it as a queued message until the box's transcript has a user message. A pane released to a fresh chat drops it, since that chat never got it.

---------

Co-authored-by: Harshith Mullapudi <harshithmullapudi@gmail.com>

* feat(devs): Devs on channels, with one actors table for people and Devs (#8953)

* feat(agents): agent identity, Melange permissions and agent sessions/turns

Agents get their own identity and access, checked with Melange inside
Postgres. Only agent access goes through it; people keep today's checks.

- 0166 agents table, authz schema, authz.agent_relations and the
  melange_tuples view; 0167 Melange's generated check/list functions;
  0168 agent_sessions (dm, channel, thread, task) and agent_turns.
- One running turn per session; settle-time columns on turns.
- agentProcedure carries ctx.agentTurn from a per-turn HMAC token;
  agentWorker routes (turnToken) use the worker secret.
- New env: AGENT_WORKER_SECRET, AGENT_TURN_SIGNING_SECRET.

* fix(agents): settle a turn only after the worker read every steer

A message that arrives while a turn runs joins it: its prompt id is
appended to agent_turns.steer_prompt_ids (a resend is a no-op).
agentWorker.settleTurn now takes steersConsumed and settles only when it
equals the number of steers, otherwise it fails with CONFLICT
"STEER_PENDING" and the worker runs one more step. A plain counter would
break on a resent steer; a `settling` status would let two turns overlap
in one session.

startDirectTurn now requires promptId and returns outcome
started | joined | duplicate, plus steerSeq when joined.

Regenerates 0168 with drizzle-kit so the SQL and snapshot match the
schema (the previous commit still created steer_count).

* feat(devs): Devs with blob avatars and DMs on channels

Devs are agents people create and talk to. A Dev's chat is a DM channel,
so it reuses the channel timeline and composer. Behind the channels flag.
Migrations are dropped here and regenerated after main's 0166.

Co-Authored-By: Claude <noreply@anthropic.com>

* feat(devs): profile sidebar instead of panes and details dialog

A Dev's page keeps its DM in the center and shows one profile sidebar:
status, what it does, how to reach it, what it is working on, and its
instructions. The DM has no thread panes. The Dev's own DM room no
longer lists as a channel it is in.

Co-Authored-By: Claude <noreply@anthropic.com>

* feat(devs): Devs as channel members, with @-mentions

A channel_members row is a person or a Dev. Melange reads a Dev's channel
access from those rows, so channels need no separate grant. Devs show in
a channel's member list, can be added and removed there, and have their
own group in the @ menu. Mentioning a Dev starts its work in the message's
thread; a reply in that thread continues it.

Co-Authored-By: Claude <noreply@anthropic.com>

* refactor(devs): one actors table for people and Devs in channels

People and Devs share an actors table; a person's actor id is their user
id and a Dev's is its agent id. Channel members, message authors and
mentions, reactions and thread follows point at actors, so each needs one
column. A backfill adds an actor for every user, and triggers add one for
each new user or Dev. Channel columns are renamed in place, so existing
data carries over.

Also removes what nothing used: agents.model, message_access, the agent
grants table, unused session kinds and turn reasons, parent sessions, and
agent.startDirectTurn. The Melange model now covers only agents,
organizations and channels. Channels and Devs come first in the sidebar.

Co-Authored-By: Claude <noreply@anthropic.com>

---------

Co-authored-by: Claude <noreply@anthropic.com>

* feat(agents): Temporal agent runtime; Devs answer in their channels (#8954)

* feat(agents): agent identity, Melange permissions and agent sessions/turns

Agents get their own identity and access, checked with Melange inside
Postgres. Only agent access goes through it; people keep today's checks.

- 0166 agents table, authz schema, authz.agent_relations and the
  melange_tuples view; 0167 Melange's generated check/list functions;
  0168 agent_sessions (dm, channel, thread, task) and agent_turns.
- One running turn per session; settle-time columns on turns.
- agentProcedure carries ctx.agentTurn from a per-turn HMAC token;
  agentWorker routes (turnToken) use the worker secret.
- New env: AGENT_WORKER_SECRET, AGENT_TURN_SIGNING_SECRET.

* fix(agents): settle a turn only after the worker read every steer

A message that arrives while a turn runs joins it: its prompt id is
appended to agent_turns.steer_prompt_ids (a resend is a no-op).
agentWorker.settleTurn now takes steersConsumed and settles only when it
equals the number of steers, otherwise it fails with CONFLICT
"STEER_PENDING" and the worker runs one more step. A plain counter would
break on a resent steer; a `settling` status would let two turns overlap
in one session.

startDirectTurn now requires promptId and returns outcome
started | joined | duplicate, plus steerSeq when joined.

Regenerates 0168 with drizzle-kit so the SQL and snapshot match the
schema (the previous commit still created steer_count).

* feat(agent-harness): Temporal agent harness, host jobs and session store (WIP)

Durable agent loop on Temporal with tools running on a box through
host-service jobs, and each session's conversation in a Durable Object.

- apps/agent-worker: agentSession workflow, model/tool/settle activities,
  dev scripts (temporal dev server, send).
- packages/agent-harness: pi events to ACP, remote pi tools, ACP to
  chat-v3 mapping.
- host-service agentJobs router: commands as jobs deduped by call id.
- realtime AgentSessionHub: ACP, events and transcript per session.
- trpc: startDirectTurn hands turns to Temporal; agentWorker
  turnCredentials checks the agent can operate the box.

* fix(agent-worker): stop reaches a silent tool and kills its host job

Temporal only delivers a cancel on a heartbeat, so model and tool steps now
heartbeat on a 5 s timer. followJob rejects as soon as the signal aborts
instead of waiting out the host long-poll.

* fix(agent-worker): local model key applies to sessions with a box

A local run can't pass the router's box credential check, so the local key
wins whenever it is set. Production never sets it.

* fix(agent-harness): edge cases found in review

- A steer for a queued turn joins it instead of running the turn twice;
  a steer for a finished turn is dropped; a stop for a queued turn ends it.
- The session log ticket is read per request, so a long tool step does not
  fail its last write after the 15 minute ticket expires.
- settleTurn returns a write ticket, also for a turn already settled, so a
  retried settle still writes the end marker.
- job_wait waits at most 25 minutes, inside the tool step's 30 minute limit.
- Tool and prompt text no longer promise a wake-up when a background job ends.
- Turn cost uses the router's prices from /v1/config, with pi's Anthropic
  catalog as the fallback; input tokens include cache reads and writes.
- Per-turn caches in the worker are dropped once the turn settles.

* fix(agent-worker): a turn without a workspace runs with no tools

It used to fail every turn. With no box it gets a short system prompt and
no tools, and calls the model with AGENT_ROUTER_API_KEY when that is set.

* fix(agent-worker): a session that lost its workspace still answers

With no tools offered, past tool calls and results reach the model as text;
otherwise it answered with nothing.

* fix(agent-worker): a turn that loses workspace access settles as failed

Settling read the transcript with full turn credentials, so a revoked grant
failed the settle, and the failed settle ended the session workflow and left
the turn running. Settling now falls back to the ticket settleTurn returns,
and a turn that cannot settle no longer ends the workflow.

* fix(agent-harness): ride out a host-service restart

Host calls retry for about a minute on 502/503/504 and network errors, so a
restart no longer turns into a tool error. A job the restart stopped tells
the model what happened instead of "Command lost".

* fix(agent-harness): refuse binary reads; summaries for ls and job tools

Reading a binary file sent up to 50KB of junk to the model; it now says the
file is binary and points to bash. Images still go through as images.

* fix(agent-worker): a model 4xx fails the turn at once

A bad model id or request retried five times (about 20s) before failing.
4xx answers other than 408 and 429 are now not retried.

* fix(agent): a prompt Temporal never received does not hold a turn open

If dispatch failed, the new turn stayed running and every later message
joined it. Now a new turn is removed and a steer is taken off its turn, so
the client can resend the same prompt id.

* feat(agent): serve agent sessions in the shape chat-v3 loads

- User messages carry the sender's prompt id as clientId, so a chat client
  confirms its send; session state says the session has no queue controls.
- The session store pages events backwards (?before=) and serves a head:
  latest cursor and session state.
- agent.listSessions lists the person's DM sessions; agent.promptSession sends
  into one by session id; sessionStream also returns the store's base URL.

* feat(chat): a chat-v3 session client for agent sessions

createAgentSessionClient reads history and the live stream from the agent
session store and sends prompts and stops through the agent API, with a fresh
read ticket on every redial. It plugs into useChatSession unchanged.

* feat(desktop): open agent sessions in the chat view

An agent session on a cloud workspace shows in that workspace's chat history
under Agents and opens as a normal chat tab: the existing SessionView with the
agent session client, so history, live streaming, sending and stop work as in
any chat.

* feat(agent-harness): post_message tool and the room reply fallback

A session with a room gets a post_message tool and is told people read only
what it posts. A completed turn that answered without posting has its reply
posted once. Posts carry an idempotency key (the tool call id, or
<turnId>:reply), so a retried step never posts twice. The poster is injected;
it gets wired to agentApi.postMessage after rebasing onto the DM work.

* feat(devs): Devs with blob avatars and DMs on channels

Devs are agents people create and talk to. A Dev's chat is a DM channel,
so it reuses the channel timeline and composer. Behind the channels flag.
Migrations are dropped here and regenerated after main's 0166.

Co-Authored-By: Claude <noreply@anthropic.com>

* feat(devs): profile sidebar instead of panes and details dialog

A Dev's page keeps its DM in the center and shows one profile sidebar:
status, what it does, how to reach it, what it is working on, and its
instructions. The DM has no thread panes. The Dev's own DM room no
longer lists as a channel it is in.

Co-Authored-By: Claude <noreply@anthropic.com>

* feat(devs): Devs as channel members, with @-mentions

A channel_members row is a person or a Dev. Melange reads a Dev's channel
access from those rows, so channels need no separate grant. Devs show in
a channel's member list, can be added and removed there, and have their
own group in the @ menu. Mentioning a Dev starts its work in the message's
thread; a reply in that thread continues it.

Co-Authored-By: Claude <noreply@anthropic.com>

* refactor(devs): one actors table for people and Devs in channels

People and Devs share an actors table; a person's actor id is their user
id and a Dev's is its agent id. Channel members, message authors and
mentions, reactions and thread follows point at actors, so each needs one
column. A backfill adds an actor for every user, and triggers add one for
each new user or Dev. Channel columns are renamed in place, so existing
data carries over.

Also removes what nothing used: agents.model, message_access, the agent
grants table, unused session kinds and turn reasons, parent sessions, and
agent.startDirectTurn. The Melange model now covers only agents,
organizations and channels. Channels and Devs come first in the sidebar.

Co-Authored-By: Claude <noreply@anthropic.com>

* fix(agent-harness): sweep old job files without blocking the host loop

Also formats a worker test.

Co-Authored-By: Claude <noreply@anthropic.com>

* test(agent-harness): wait for the job sweep before checking it

Co-Authored-By: Claude <noreply@anthropic.com>

---------

Co-authored-by: Claude <noreply@anthropic.com>

* fix(shared): one machine always reports one host id (#8950)

A desktop and the host-service it spawns could compute different host
ids. The renderer then treated the machine's own workspaces as remote,
routed terminals through the relay, and got host-offline 503s, because
a local host-service is not on the relay by default.

- getHostId prefers HOST_CLIENT_ID, which the desktop already passes to
  its host-service, so parent and child cannot disagree.
- Call ioreg by absolute path. The host-service gets the login-shell
  PATH, which can lack /usr/sbin; a failed ioreg silently fell back to a
  hostname-based id in that process only.
- Warn when the hostname fallback is used, so the next case is visible.

Co-authored-by: Harshith Mullapudi <harshithmullapudi@gmail.com>

* chore: bundle pdev 0.2.2 in the desktop app and cloud boxes (#8960)

pdev 0.2.2 is built on pi 1.4.0. The sandbox row now points at the
ParadevAI/pdev release; superset-sh/pdev redirects there.

* feat(desktop): merge Pages list and page viewer into one right-pane pane (#8959)

* feat(desktop): merge Pages list and page viewer into one right-pane pane

Opening a page from the Pages list now shows the viewer in the same pane,
with a back button in the header. The list stays mounted under the viewer,
so its search query and scroll position survive the round trip.

* fix(desktop): style the Pages back button like other pane header buttons

Use the pane header button style (muted p-1 icon button, LuArrowLeft,
bottom tooltip) that the file, comment and pull request panes use, in
place of the ad hoc chevron.

* chore(cli,plugins): say Paradev in CLI, SDK, host-service and plugin copy (#8969)

- Plugin manifests and the marketplace: author Paradev, paradev.com and
  docs.paradev.com links. 17 marketplace plugins bumped a patch version.
- Bundled superset plugin: skill and command prose, display names and
  install/docs links say Paradev; .claude-plugin description gets the
  intro name.
- SDK README/docs comments and homepage, CLI spec docs.
- Codex/Grok managed-block comments written into agent configs.
- Project config error prefix and cloud sandbox git author use BRAND.NAME.

* chore(app): say Paradev in app UI copy (#8968)

Workspace config settings strings, the config.toml template, the chat
reference preamble sent to agents, App Store links in store.config.js,
and the mobile agent/release runbook prose now say Paradev. All 17
locales filled.

* chore(marketing,email,agents): say Paradev in marketing, email, and agent docs copy (#8967)

Marketing compare/category pages link to paradev.com, docs.paradev.com and
github.com/paradevai/paradev. The CLI installer header, theme page keywords,
email README, AGENTS.md files, .agents commands and skills, the issue
template docs link, and workflow display names say Paradev.

Co-authored-by: Claude <noreply@anthropic.com>

* chore(docs): say Paradev in docs and README copy (#8966)

* chore(docs): say Paradev in docs and README copy

Move the remaining product-name prose and marketing/docs links in
apps/docs, package READMEs and contributor docs to Paradev and
paradev.com. Identifiers, CLI commands, SDK imports, release file
names, API/status hosts and historical notes stay as they are.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* chore(docs): move remaining docs links and notes to Paradev

Status page, email README hosts, CLI install URL, GitHub links and
prose in internal notes. api.superset.sh stays, as do hosts with no
paradev.com twin (relay, realtime, cdn, router) and the email sender.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>

* fix(desktop): keep the chat scroll position when a chat resumes (#8957)

* fix(desktop): keep the chat scroll position when a chat resumes

A resume replaces the chat's session, and SessionView is keyed by the
session id, so the transcript remounted and the scroller put the reader
at the end. The old transcript now records the reader's place, the
resume carries it to the replacement session, and the new transcript
scrolls back to it. The replay mints new item ids, so the place is
stored as a user message (place from the end and text) plus an offset.

* fix(desktop): no blank transcript while a resumed chat swaps sessions

The view of the replacement session mounted with an empty snapshot and
stayed blank until its first getSession + getOutline round trip, 300 to
500 ms locally. The replacement's transcr…

This branch was previously deployed

1 inactive deployment
preview — 751d5985 Deployed Oct 27, 2026 by superset-home via Deploy API #12923
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant