1535
*/
1536
private async _sendTurnMessage(options: {
1538
>
/** The agent/session URI the chat lives on (the send target). */
1539
>
sessionChannel: ProtocolURI;
1540
>
/** The channel the turn runs on — where `ChatError` / turn completion are reported. */
1541
>
turnChannel: ProtocolURI;
1542
>
/** Chat channel URI the turn targets. */
1543
>
chat: ProtocolURI;
1544
>
message: Message;
1545
>
turnId: string;
1546
>
senderClientId: string | undefined;
1547
>
turnStopWatch: StopWatch;
1548
>
}): Promise<void> {
1549
>
const { agent, sessionChannel, turnChannel, chat, message, turnId, senderClientId, turnStopWatch } = options;
1550
>
1551
>
// Read-only chats reject user-dispatched turns. `interactivity` is the
1552
>
// general signal (e.g. subagent worker chats are `ReadOnly`), and an
1553
>
// archived session downgrades its interactive chats to read-only too — so
1554
>
// enforce off the chat's effective interactivity rather than special-casing
1555
>
// archived. This is the enforcement behind the UI hiding the composer, so a
1556
>
// buggy or remote client cannot run work in a read-only or archived session
1557
>
// (which may no longer have its isolated worktree on disk).
1558
>
const chatState = this._stateManager.getChatState(chat);
1559
>
const sessionStatus = this._stateManager.getSessionSummary(options.sessionChannel)?.status ?? 0;
1560
>
const sessionArchived = (sessionStatus & SessionStatus.IsArchived) === SessionStatus.IsArchived;
1561
>
if (isChatReadOnly(chatState?.interactivity, sessionArchived)) {
1562
const error = sessionArchived
1563
? { errorType: 'archived', message: 'This session is archived and read-only. Restore the session to continue the conversation.' }