Channels: default-only threading + automation (read-only) channels — threading (topic switcher, Ctrl+T, auto-threading) is now only active on the default channel, and the gateway ignores auto_thread for other channels; new channel.set_automation frame marks a channel read-only for cron/webhook output (app hides the composer behind a notice, gateway rejects message.send, default channel cannot be marked, flag syncs to all devices via the full-entry channel.renamed response, gear badge in the list); header pill drops the 'Bot' subtitle and shows the chat_id on automation channels (tap to copy for cron delivery targeting); also fixes a pre-existing stray brace that made frames.schema.json invalid JSON
This commit is contained in:
1 parent
d7cd59b685
commit
10e9565e2e
9 files changed
+256
-37
No files matched your search
@@ -1702,6 +1702,23 @@ class AndroidAdapter(BasePlatformAdapter):
|
||||
chat_id = self.home_channel
|
||||
chat_id = chat_id.strip()
|
||||
|
||||
# Automation channels are read-only for the user: they only receive
|
||||
# gateway-originated output (cron jobs, webhooks). Reject direct sends
|
||||
# (the app hides the composer for them, this is the server-side
|
||||
# enforcement).
|
||||
target = self._channels.get(chat_id)
|
||||
if target is not None and target.get("automation"):
|
||||
await self._ws_server.send_to(
|
||||
device_id,
|
||||
protocol.error(
|
||||
protocol.ERR_UNSUPPORTED,
|
||||
f"{target.get('name') or chat_id} is an automation channel "
|
||||
"(read-only: cron/webhook output only)",
|
||||
id=frame.id,
|
||||
),
|
||||
)
|
||||
return
|
||||
|
||||
thread_id = frame.thread_id or payload.get("thread_id")
|
||||
if not isinstance(thread_id, str) or not thread_id.strip():
|
||||
thread_id = None
|
||||
@@ -1717,14 +1734,18 @@ class AndroidAdapter(BasePlatformAdapter):
|
||||
# LLM upgrades the name in the background. The user echo, the agent
|
||||
# turn, and all streaming frames then carry the new thread_id.
|
||||
# Skipped for slash commands (session-scoped, not conversation
|
||||
# starters) and replies (they continue where the user is).
|
||||
# starters) and replies (they continue where the user is). Threading
|
||||
# is only active on the default channel; other channels stay flat.
|
||||
auto_thread = payload.get("auto_thread") is True
|
||||
default_entry = self._channels.default()
|
||||
if (
|
||||
auto_thread
|
||||
and thread_id is None
|
||||
and text.strip()
|
||||
and not text.lstrip().startswith("/")
|
||||
and reply_to is None
|
||||
and default_entry is not None
|
||||
and chat_id == default_entry["chat_id"]
|
||||
):
|
||||
entry = self._channels.create(
|
||||
name=_derive_thread_name(text),
|
||||
@@ -2160,6 +2181,34 @@ class AndroidAdapter(BasePlatformAdapter):
|
||||
resp.id = frame.id
|
||||
await self._ws_server.broadcast(resp)
|
||||
|
||||
async def on_channel_set_automation(self, frame: protocol.Frame, device_id: str) -> None:
|
||||
chat_id = frame.chat_id or frame.payload.get("chat_id")
|
||||
if not isinstance(chat_id, str) or not chat_id.strip():
|
||||
await self._ws_server.send_to(
|
||||
device_id,
|
||||
protocol.error(
|
||||
protocol.ERR_NOT_FOUND, "channel.set_automation requires chat_id", id=frame.id
|
||||
),
|
||||
)
|
||||
return
|
||||
on = bool(frame.payload.get("on"))
|
||||
entry = self._channels.set_automation(chat_id, on)
|
||||
if entry is None:
|
||||
await self._ws_server.send_to(
|
||||
device_id,
|
||||
protocol.error(
|
||||
protocol.ERR_NOT_FOUND,
|
||||
f"cannot set automation on {chat_id} (unknown or default)",
|
||||
id=frame.id,
|
||||
),
|
||||
)
|
||||
return
|
||||
# Reuse the renamed event shape: it carries the full entry (incl. the
|
||||
# new automation flag) so every device reconciles the change.
|
||||
resp = protocol.channel_renamed(entry)
|
||||
resp.id = frame.id
|
||||
await self._ws_server.broadcast(resp)
|
||||
|
||||
async def on_channel_delete(self, frame: protocol.Frame, device_id: str) -> None:
|
||||
chat_id = frame.chat_id or frame.payload.get("chat_id")
|
||||
if not isinstance(chat_id, str) or not chat_id.strip():
|
||||
|
||||
Reference in new issue
Block a user