archastro.platform.channels.api_tasks_channel
1# Copyright (c) 2026 ArchAstro Inc. Licensed under the MIT License. 2# This file is auto-generated by @archastro/sdk-generator. Do not edit. 3# Content hash: 923456011b8a 4 5from collections.abc import Callable 6from datetime import datetime 7from typing import TYPE_CHECKING, Any, Required, TypedDict 8 9if TYPE_CHECKING: 10 from archastro.phx_channel.socket import Socket 11 12 13class TasksUpdatedPayloadTasksItemCreatedByActorProfilePicture(TypedDict, total=False): 14 file: str | None 15 "ID of the underlying storage file (`fil_...`). `null` when the image is not backed by a platform storage file." 16 height: int | None 17 "Height of the image in pixels. `null` if not known." 18 media: str | None 19 "ID of the associated media record (`med_...`). `null` when the image is not linked to a media entity." 20 mime_type: str | None 21 'MIME type of the image, e.g. `"image/png"` or `"image/jpeg"`. `null` if not known.' 22 refresh_url: str | None 23 "Endpoint URL you can call to obtain a fresh signed `url` when the current one has expired. `null` if the URL does not require refreshing." 24 url: str | None 25 "Signed or public URL for downloading the image. May be time-limited; use `refresh_url` to obtain a new URL when this one expires." 26 width: int | None 27 "Width of the image in pixels. `null` if not known." 28 29 30class TasksUpdatedPayloadTasksItemCreatedByActor(TypedDict, total=False): 31 alias: str | None 32 "Short handle or alias for the actor, used as an alternate display identifier. `null` if not configured." 33 id: str | None 34 'Composite actor identifier. Format is `"user-<usr_...>"` for human users or `"agent-<agi_...>"` for agents.' 35 name: str | None 36 "Display name of the actor shown in the UI. `null` if no name is set." 37 profile_picture: TasksUpdatedPayloadTasksItemCreatedByActorProfilePicture | None 38 "Profile picture for the actor. `null` if the actor has no profile picture." 39 40 41class TasksUpdatedPayloadTasksItemCurrentLease(TypedDict): 42 expires_at: datetime 43 "Server-calculated lease expiry in ISO 8601 format." 44 harness: str 45 "Bounded harness identifier for the coding session." 46 session_name: str 47 "Display name supplied by the coding session that holds the lease." 48 49 50class TasksUpdatedPayloadTasksItemOwnerActorProfilePicture(TypedDict, total=False): 51 file: str | None 52 "ID of the underlying storage file (`fil_...`). `null` when the image is not backed by a platform storage file." 53 height: int | None 54 "Height of the image in pixels. `null` if not known." 55 media: str | None 56 "ID of the associated media record (`med_...`). `null` when the image is not linked to a media entity." 57 mime_type: str | None 58 'MIME type of the image, e.g. `"image/png"` or `"image/jpeg"`. `null` if not known.' 59 refresh_url: str | None 60 "Endpoint URL you can call to obtain a fresh signed `url` when the current one has expired. `null` if the URL does not require refreshing." 61 url: str | None 62 "Signed or public URL for downloading the image. May be time-limited; use `refresh_url` to obtain a new URL when this one expires." 63 width: int | None 64 "Width of the image in pixels. `null` if not known." 65 66 67class TasksUpdatedPayloadTasksItemOwnerActor(TypedDict, total=False): 68 alias: str | None 69 "Short handle or alias for the actor, used as an alternate display identifier. `null` if not configured." 70 id: str | None 71 'Composite actor identifier. Format is `"user-<usr_...>"` for human users or `"agent-<agi_...>"` for agents.' 72 name: str | None 73 "Display name of the actor shown in the UI. `null` if no name is set." 74 profile_picture: TasksUpdatedPayloadTasksItemOwnerActorProfilePicture | None 75 "Profile picture for the actor. `null` if the actor has no profile picture." 76 77 78class TasksUpdatedPayloadTasksItem(TypedDict, total=False): 79 agent: str | None 80 "ID of the agent that owns this task (`agi_...`). `null` if the task is scoped to a team or user." 81 aggregate_version: int | None 82 "Event-stream version for optimistic update fencing. Pass this value as `expected_version` on an update to reject concurrent writes." 83 blocked_by_count: int | None 84 "Number of readable tasks marked as blocking this task, whether or not they are done (see `GET /tasks/{task}/blockers`). Computed on list/show reads; create/update responses may lag one read behind." 85 closed_at: datetime | None 86 "When the task was marked as done or otherwise closed (ISO 8601). `null` if the task is still open." 87 comments_count: int | None 88 "Total number of comments posted on this task." 89 created_at: datetime | None 90 "When the task was created (ISO 8601)." 91 created_by_actor: TasksUpdatedPayloadTasksItemCreatedByActor | None 92 "Resolved creator details including `id`, `name`, `alias`, and `profile_picture`. `null` if no creator is set or the creator cannot be resolved (e.g. creating agent was deleted)." 93 created_by_agent: str | None 94 "ID of the agent that created this task (`agi_...`). `null` if the task was created by a human user, or if the creating agent was later deleted." 95 created_by_user: str | None 96 "ID of the user who created this task (`usr_...`). `null` if the task was created by an agent, or if creator provenance was cleared after the creator was deleted." 97 current_lease: TasksUpdatedPayloadTasksItemCurrentLease | None 98 "Viewer-safe live coding-session lease summary. `null` when the task is unleased or the projected lease has expired. Fencing identifiers are never included." 99 description: str | None 100 "Long-form description or notes for the task. `null` if no description has been provided." 101 due_date: datetime | None 102 "Date and time by which the task should be completed (ISO 8601). `null` if no due date is set." 103 epic: str | None 104 "Free-form grouping label. `null` when the task is not in an epic." 105 id: Required[str] 106 "Task ID (`tsk_...`)." 107 is_blocked: bool | None 108 "`true` while at least one blocking task is not yet done. Informational only - a blocked task can still change status - and derived at read time, so the task un-blocks automatically when its last open blocker completes. Computed on reads, including mutation responses." 109 is_stalled: bool | None 110 "True exactly when status is in_progress and the task has no lease or its lease expiry is in the past. Derived at read time from status and lease expiry, including on unannotated reads." 111 links: dict[str, Any] | None 112 "Key-value map of named URLs or references associated with the task. Returns an empty object when no links have been set." 113 metadata: dict[str, Any] | None 114 "Arbitrary key-value map of application-specific data stored alongside the task. Returns an empty object when no metadata has been set." 115 name: Required[str] 116 "Human-readable title of the task." 117 org: str | None 118 "ID of the organization this task belongs to (`org_...`). `null` for tasks outside an org context." 119 owner_actor: TasksUpdatedPayloadTasksItemOwnerActor | None 120 "Resolved owner details including `id`, `name`, `alias`, and `profile_picture`. `null` if the task is unassigned or the owner cannot be resolved (e.g. assigned agent was deleted)." 121 owner_agent: str | None 122 "ID of the agent assigned as owner (`agi_...`). `null` if the owner is a human user, the task is unassigned, or the assigned agent was deleted." 123 owner_user: str | None 124 "ID of the user assigned as owner (`usr_...`). `null` if the owner is an agent, the task is unassigned, or the assigned agent was deleted." 125 parent: str | None 126 "ID of the parent task when this task is a subtask (`tsk_...`). `null` for top-level tasks or when the parent is not readable. Subtasks nest exactly one level." 127 priority: int | None 128 "Priority level of the task from `0` (highest) to `4` (lowest). Defaults to `2` (medium) when not explicitly set." 129 project_id: str | None 130 "Optional project ID (`prj_...`). Membership does not change task visibility." 131 sandbox: str | None 132 "ID of the developer sandbox this task is scoped to (`dsb_...`). `null` for tasks outside a sandbox environment." 133 source_id: str | None 134 "Source object identity (for example `ArchAstro/firstlanding`). `null` when the task has no source." 135 source_scope: str | None 136 "Container of the work this task is about (for example `github.com`). `null` when the task has no source. Set together with `source_type` and `source_id`." 137 source_type: str | None 138 "Kind of source object (for example `repository`). `null` when the task has no source." 139 stalled_since: datetime | None 140 "Lease expiry for a stalled task, or its last update when no lease expiry exists. Null when not stalled." 141 status: Required[str] 142 'Current status of the task. One of `"open"`, `"in_progress"`, `"in_review"`, `"paused"`, `"failed"`, `"superseding"`, `"done"`, or `"cancelled"`.' 143 subtasks_count: int | None 144 "Number of readable subtasks under this task. Computed on list/show reads; create/update responses may report 0 until the next read. Always 0 for subtasks." 145 tags: list[str] | None 146 "Labels for grouping and filtering, stored lowercase and de-duplicated. Empty array when untagged." 147 target_binary: str | None 148 "Binary that must run this work (for example `my-cli`). A worker should take the task only when its own `target_binary` build is at least `target_version`. `null` when the task has no target. Set together with `target_version`." 149 target_version: str | None 150 "Minimum semantic version of `target_binary` that can run this work (for example `0.48.9`), in canonical `MAJOR.MINOR.PATCH[-prerelease][+build]` form. `null` when the task has no target." 151 team: str | None 152 "ID of the team that owns this task (`tem_...`). `null` if the task is not scoped to a team." 153 thread: str | None 154 "ID of the thread this task is bound to (`thr_...`) - the conversation it was filed from, or the thread passed at creation. `null` for tasks not tied to a thread." 155 updated_at: datetime | None 156 "When the task was last modified (ISO 8601)." 157 user: str | None 158 "ID of the user that owns this task (`usr_...`). `null` if the task is scoped to a team." 159 160 161class TasksUpdatedPayload(TypedDict, total=False): 162 "Broadcast when the thread's task records change; carries a full snapshot" 163 164 tasks: list[TasksUpdatedPayloadTasksItem] | None 165 166 167# Phoenix channel for live task-record updates. 168# Clients join `api:tasks:thread:{thread_id}` to receive the thread's current 169# event-sourced tasks and subscribe to changes. The join reply carries a full 170# snapshot (`%{tasks: [...]}`), and every subsequent change lands as a 171# `tasks_updated` push carrying a fresh snapshot the same replace-wholesale 172# contract expected by task-panel clients, so clients need no 173# delta bookkeeping. 174# Change signals originate from `ArchAstro.Tasks.Projectors.TaskProjector`, 175# which broadcasts on `"tasks:thread:{thread_id}"` after each committed 176# projection. Bursts (e.g. a mirror reconcile dispatching several commands) 177# are coalesced: the first signal arms a short timer and the reload happens 178# once, after the burst settles. 179class ApiTasksChannel: 180 def __init__(self, channel, join_response=None): 181 self._channel = channel 182 self.join_response = join_response 183 184 # Join a thread's live task list 185 @staticmethod 186 def topic_thread(thread_id: str) -> str: 187 return f"api:tasks:thread:{thread_id}" 188 189 # Join a thread's live task list 190 @classmethod 191 async def join_thread(cls, socket: "Socket", thread_id: str) -> "ApiTasksChannel": 192 topic = cls.topic_thread(thread_id) 193 channel = socket.channel(topic) 194 join_response = await channel.join() 195 return cls(channel, join_response) 196 197 # Leave the underlying channel. 198 async def leave(self): 199 await self._channel.leave() 200 201 # Broadcast when the thread's task records change; carries a full snapshot 202 def on_tasks_updated( 203 self, callback: Callable[[TasksUpdatedPayload], None] 204 ) -> Callable[[], None]: 205 return self._channel.on("tasks_updated", callback)
14class TasksUpdatedPayloadTasksItemCreatedByActorProfilePicture(TypedDict, total=False): 15 file: str | None 16 "ID of the underlying storage file (`fil_...`). `null` when the image is not backed by a platform storage file." 17 height: int | None 18 "Height of the image in pixels. `null` if not known." 19 media: str | None 20 "ID of the associated media record (`med_...`). `null` when the image is not linked to a media entity." 21 mime_type: str | None 22 'MIME type of the image, e.g. `"image/png"` or `"image/jpeg"`. `null` if not known.' 23 refresh_url: str | None 24 "Endpoint URL you can call to obtain a fresh signed `url` when the current one has expired. `null` if the URL does not require refreshing." 25 url: str | None 26 "Signed or public URL for downloading the image. May be time-limited; use `refresh_url` to obtain a new URL when this one expires." 27 width: int | None 28 "Width of the image in pixels. `null` if not known."
ID of the underlying storage file (fil_...). null when the image is not backed by a platform storage file.
ID of the associated media record (med_...). null when the image is not linked to a media entity.
Endpoint URL you can call to obtain a fresh signed url when the current one has expired. null if the URL does not require refreshing.
Signed or public URL for downloading the image. May be time-limited; use refresh_url to obtain a new URL when this one expires.
31class TasksUpdatedPayloadTasksItemCreatedByActor(TypedDict, total=False): 32 alias: str | None 33 "Short handle or alias for the actor, used as an alternate display identifier. `null` if not configured." 34 id: str | None 35 'Composite actor identifier. Format is `"user-<usr_...>"` for human users or `"agent-<agi_...>"` for agents.' 36 name: str | None 37 "Display name of the actor shown in the UI. `null` if no name is set." 38 profile_picture: TasksUpdatedPayloadTasksItemCreatedByActorProfilePicture | None 39 "Profile picture for the actor. `null` if the actor has no profile picture."
Short handle or alias for the actor, used as an alternate display identifier. null if not configured.
Composite actor identifier. Format is "user-<usr_...>" for human users or "agent-<agi_...>" for agents.
Profile picture for the actor. null if the actor has no profile picture.
42class TasksUpdatedPayloadTasksItemCurrentLease(TypedDict): 43 expires_at: datetime 44 "Server-calculated lease expiry in ISO 8601 format." 45 harness: str 46 "Bounded harness identifier for the coding session." 47 session_name: str 48 "Display name supplied by the coding session that holds the lease."
51class TasksUpdatedPayloadTasksItemOwnerActorProfilePicture(TypedDict, total=False): 52 file: str | None 53 "ID of the underlying storage file (`fil_...`). `null` when the image is not backed by a platform storage file." 54 height: int | None 55 "Height of the image in pixels. `null` if not known." 56 media: str | None 57 "ID of the associated media record (`med_...`). `null` when the image is not linked to a media entity." 58 mime_type: str | None 59 'MIME type of the image, e.g. `"image/png"` or `"image/jpeg"`. `null` if not known.' 60 refresh_url: str | None 61 "Endpoint URL you can call to obtain a fresh signed `url` when the current one has expired. `null` if the URL does not require refreshing." 62 url: str | None 63 "Signed or public URL for downloading the image. May be time-limited; use `refresh_url` to obtain a new URL when this one expires." 64 width: int | None 65 "Width of the image in pixels. `null` if not known."
ID of the underlying storage file (fil_...). null when the image is not backed by a platform storage file.
ID of the associated media record (med_...). null when the image is not linked to a media entity.
Endpoint URL you can call to obtain a fresh signed url when the current one has expired. null if the URL does not require refreshing.
Signed or public URL for downloading the image. May be time-limited; use refresh_url to obtain a new URL when this one expires.
68class TasksUpdatedPayloadTasksItemOwnerActor(TypedDict, total=False): 69 alias: str | None 70 "Short handle or alias for the actor, used as an alternate display identifier. `null` if not configured." 71 id: str | None 72 'Composite actor identifier. Format is `"user-<usr_...>"` for human users or `"agent-<agi_...>"` for agents.' 73 name: str | None 74 "Display name of the actor shown in the UI. `null` if no name is set." 75 profile_picture: TasksUpdatedPayloadTasksItemOwnerActorProfilePicture | None 76 "Profile picture for the actor. `null` if the actor has no profile picture."
Short handle or alias for the actor, used as an alternate display identifier. null if not configured.
Composite actor identifier. Format is "user-<usr_...>" for human users or "agent-<agi_...>" for agents.
Profile picture for the actor. null if the actor has no profile picture.
79class TasksUpdatedPayloadTasksItem(TypedDict, total=False): 80 agent: str | None 81 "ID of the agent that owns this task (`agi_...`). `null` if the task is scoped to a team or user." 82 aggregate_version: int | None 83 "Event-stream version for optimistic update fencing. Pass this value as `expected_version` on an update to reject concurrent writes." 84 blocked_by_count: int | None 85 "Number of readable tasks marked as blocking this task, whether or not they are done (see `GET /tasks/{task}/blockers`). Computed on list/show reads; create/update responses may lag one read behind." 86 closed_at: datetime | None 87 "When the task was marked as done or otherwise closed (ISO 8601). `null` if the task is still open." 88 comments_count: int | None 89 "Total number of comments posted on this task." 90 created_at: datetime | None 91 "When the task was created (ISO 8601)." 92 created_by_actor: TasksUpdatedPayloadTasksItemCreatedByActor | None 93 "Resolved creator details including `id`, `name`, `alias`, and `profile_picture`. `null` if no creator is set or the creator cannot be resolved (e.g. creating agent was deleted)." 94 created_by_agent: str | None 95 "ID of the agent that created this task (`agi_...`). `null` if the task was created by a human user, or if the creating agent was later deleted." 96 created_by_user: str | None 97 "ID of the user who created this task (`usr_...`). `null` if the task was created by an agent, or if creator provenance was cleared after the creator was deleted." 98 current_lease: TasksUpdatedPayloadTasksItemCurrentLease | None 99 "Viewer-safe live coding-session lease summary. `null` when the task is unleased or the projected lease has expired. Fencing identifiers are never included." 100 description: str | None 101 "Long-form description or notes for the task. `null` if no description has been provided." 102 due_date: datetime | None 103 "Date and time by which the task should be completed (ISO 8601). `null` if no due date is set." 104 epic: str | None 105 "Free-form grouping label. `null` when the task is not in an epic." 106 id: Required[str] 107 "Task ID (`tsk_...`)." 108 is_blocked: bool | None 109 "`true` while at least one blocking task is not yet done. Informational only - a blocked task can still change status - and derived at read time, so the task un-blocks automatically when its last open blocker completes. Computed on reads, including mutation responses." 110 is_stalled: bool | None 111 "True exactly when status is in_progress and the task has no lease or its lease expiry is in the past. Derived at read time from status and lease expiry, including on unannotated reads." 112 links: dict[str, Any] | None 113 "Key-value map of named URLs or references associated with the task. Returns an empty object when no links have been set." 114 metadata: dict[str, Any] | None 115 "Arbitrary key-value map of application-specific data stored alongside the task. Returns an empty object when no metadata has been set." 116 name: Required[str] 117 "Human-readable title of the task." 118 org: str | None 119 "ID of the organization this task belongs to (`org_...`). `null` for tasks outside an org context." 120 owner_actor: TasksUpdatedPayloadTasksItemOwnerActor | None 121 "Resolved owner details including `id`, `name`, `alias`, and `profile_picture`. `null` if the task is unassigned or the owner cannot be resolved (e.g. assigned agent was deleted)." 122 owner_agent: str | None 123 "ID of the agent assigned as owner (`agi_...`). `null` if the owner is a human user, the task is unassigned, or the assigned agent was deleted." 124 owner_user: str | None 125 "ID of the user assigned as owner (`usr_...`). `null` if the owner is an agent, the task is unassigned, or the assigned agent was deleted." 126 parent: str | None 127 "ID of the parent task when this task is a subtask (`tsk_...`). `null` for top-level tasks or when the parent is not readable. Subtasks nest exactly one level." 128 priority: int | None 129 "Priority level of the task from `0` (highest) to `4` (lowest). Defaults to `2` (medium) when not explicitly set." 130 project_id: str | None 131 "Optional project ID (`prj_...`). Membership does not change task visibility." 132 sandbox: str | None 133 "ID of the developer sandbox this task is scoped to (`dsb_...`). `null` for tasks outside a sandbox environment." 134 source_id: str | None 135 "Source object identity (for example `ArchAstro/firstlanding`). `null` when the task has no source." 136 source_scope: str | None 137 "Container of the work this task is about (for example `github.com`). `null` when the task has no source. Set together with `source_type` and `source_id`." 138 source_type: str | None 139 "Kind of source object (for example `repository`). `null` when the task has no source." 140 stalled_since: datetime | None 141 "Lease expiry for a stalled task, or its last update when no lease expiry exists. Null when not stalled." 142 status: Required[str] 143 'Current status of the task. One of `"open"`, `"in_progress"`, `"in_review"`, `"paused"`, `"failed"`, `"superseding"`, `"done"`, or `"cancelled"`.' 144 subtasks_count: int | None 145 "Number of readable subtasks under this task. Computed on list/show reads; create/update responses may report 0 until the next read. Always 0 for subtasks." 146 tags: list[str] | None 147 "Labels for grouping and filtering, stored lowercase and de-duplicated. Empty array when untagged." 148 target_binary: str | None 149 "Binary that must run this work (for example `my-cli`). A worker should take the task only when its own `target_binary` build is at least `target_version`. `null` when the task has no target. Set together with `target_version`." 150 target_version: str | None 151 "Minimum semantic version of `target_binary` that can run this work (for example `0.48.9`), in canonical `MAJOR.MINOR.PATCH[-prerelease][+build]` form. `null` when the task has no target." 152 team: str | None 153 "ID of the team that owns this task (`tem_...`). `null` if the task is not scoped to a team." 154 thread: str | None 155 "ID of the thread this task is bound to (`thr_...`) - the conversation it was filed from, or the thread passed at creation. `null` for tasks not tied to a thread." 156 updated_at: datetime | None 157 "When the task was last modified (ISO 8601)." 158 user: str | None 159 "ID of the user that owns this task (`usr_...`). `null` if the task is scoped to a team."
ID of the agent that owns this task (agi_...). null if the task is scoped to a team or user.
Event-stream version for optimistic update fencing. Pass this value as expected_version on an update to reject concurrent writes.
Number of readable tasks marked as blocking this task, whether or not they are done (see GET /tasks/{task}/blockers). Computed on list/show reads; create/update responses may lag one read behind.
When the task was marked as done or otherwise closed (ISO 8601). null if the task is still open.
ID of the agent that created this task (agi_...). null if the task was created by a human user, or if the creating agent was later deleted.
ID of the user who created this task (usr_...). null if the task was created by an agent, or if creator provenance was cleared after the creator was deleted.
Viewer-safe live coding-session lease summary. null when the task is unleased or the projected lease has expired. Fencing identifiers are never included.
Long-form description or notes for the task. null if no description has been provided.
Date and time by which the task should be completed (ISO 8601). null if no due date is set.
true while at least one blocking task is not yet done. Informational only - a blocked task can still change status - and derived at read time, so the task un-blocks automatically when its last open blocker completes. Computed on reads, including mutation responses.
True exactly when status is in_progress and the task has no lease or its lease expiry is in the past. Derived at read time from status and lease expiry, including on unannotated reads.
Key-value map of named URLs or references associated with the task. Returns an empty object when no links have been set.
Arbitrary key-value map of application-specific data stored alongside the task. Returns an empty object when no metadata has been set.
ID of the organization this task belongs to (org_...). null for tasks outside an org context.
ID of the agent assigned as owner (agi_...). null if the owner is a human user, the task is unassigned, or the assigned agent was deleted.
ID of the user assigned as owner (usr_...). null if the owner is an agent, the task is unassigned, or the assigned agent was deleted.
ID of the parent task when this task is a subtask (tsk_...). null for top-level tasks or when the parent is not readable. Subtasks nest exactly one level.
Priority level of the task from 0 (highest) to 4 (lowest). Defaults to 2 (medium) when not explicitly set.
ID of the developer sandbox this task is scoped to (dsb_...). null for tasks outside a sandbox environment.
Source object identity (for example ArchAstro/firstlanding). null when the task has no source.
Container of the work this task is about (for example github.com). null when the task has no source. Set together with source_type and source_id.
Kind of source object (for example repository). null when the task has no source.
Lease expiry for a stalled task, or its last update when no lease expiry exists. Null when not stalled.
Current status of the task. One of "open", "in_progress", "in_review", "paused", "failed", "superseding", "done", or "cancelled".
Number of readable subtasks under this task. Computed on list/show reads; create/update responses may report 0 until the next read. Always 0 for subtasks.
Binary that must run this work (for example my-cli). A worker should take the task only when its own target_binary build is at least target_version. null when the task has no target. Set together with target_version.
Minimum semantic version of target_binary that can run this work (for example 0.48.9), in canonical MAJOR.MINOR.PATCH[-prerelease][+build] form. null when the task has no target.
ID of the team that owns this task (tem_...). null if the task is not scoped to a team.
162class TasksUpdatedPayload(TypedDict, total=False): 163 "Broadcast when the thread's task records change; carries a full snapshot" 164 165 tasks: list[TasksUpdatedPayloadTasksItem] | None
Broadcast when the thread's task records change; carries a full snapshot
180class ApiTasksChannel: 181 def __init__(self, channel, join_response=None): 182 self._channel = channel 183 self.join_response = join_response 184 185 # Join a thread's live task list 186 @staticmethod 187 def topic_thread(thread_id: str) -> str: 188 return f"api:tasks:thread:{thread_id}" 189 190 # Join a thread's live task list 191 @classmethod 192 async def join_thread(cls, socket: "Socket", thread_id: str) -> "ApiTasksChannel": 193 topic = cls.topic_thread(thread_id) 194 channel = socket.channel(topic) 195 join_response = await channel.join() 196 return cls(channel, join_response) 197 198 # Leave the underlying channel. 199 async def leave(self): 200 await self._channel.leave() 201 202 # Broadcast when the thread's task records change; carries a full snapshot 203 def on_tasks_updated( 204 self, callback: Callable[[TasksUpdatedPayload], None] 205 ) -> Callable[[], None]: 206 return self._channel.on("tasks_updated", callback)