@maudecode/talaria-web-contracts 1.13.0 → 1.15.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/views.js CHANGED
@@ -37,11 +37,20 @@ export const MessageContentSchema = z.union([z.string(), z.array(ContentPartSche
37
37
  export const MessageRoleSchema = z.enum(['user', 'assistant', 'system', 'tool']);
38
38
  /** Persisted rows have integer ids; live rows carry string ids. */
39
39
  export const MessageIdSchema = z.union([z.string(), z.number()]);
40
+ /**
41
+ * TAL-372: the background work a delegation tool call started, on its scene row: the session's task ids it links to, their
42
+ * combined status, and the subagent counts the row shows in place ("3 subagents · 2 done, 1 failed").
43
+ */
44
+ export const BackgroundLinkSchema = z.object({
45
+ task_ids: z.array(z.string()),
46
+ status: z.enum(['running', 'attention', 'completed', 'failed', 'cancelled', 'unknown']),
47
+ agents: z.object({ total: z.number().int(), completed: z.number().int(), failed: z.number().int(), running: z.number().int() }),
48
+ });
40
49
  /** One normalized activity row: the server decides role, order, tool completion/error, and steering consumption. */
41
50
  export const ActivitySceneRowSchema = z.looseObject({
42
51
  row_id: z.string(), order_index: z.number().int(), role: z.enum(['prose', 'reasoning', 'tool', 'steering']), created_at: z.number().optional(),
43
52
  text: z.string().optional(), titles: z.array(z.string()).optional(),
44
- tool: z.looseObject({ id: z.string(), name: z.string(), ...ToolDisplayFields, args: Json.optional(), preview: z.string().nullable(), result: Json.optional(), done: z.boolean(), is_error: z.boolean(), duration: z.number().nullable(), cost_usd: z.number().nullable() }).optional(),
53
+ tool: z.looseObject({ id: z.string(), name: z.string(), ...ToolDisplayFields, args: Json.optional(), preview: z.string().nullable(), result: Json.optional(), done: z.boolean(), is_error: z.boolean(), duration: z.number().nullable(), cost_usd: z.number().nullable(), background: BackgroundLinkSchema.optional() }).optional(),
45
54
  steering: z.looseObject({ steer_id: z.string(), consumed: z.boolean(), submitted_at: z.number().nullable(), consumed_at: z.number().nullable(), phase_duration: z.number().nullable().optional() }).optional(),
46
55
  });
47
56
  /** One file a turn changed: its normalized path as the tool reported it, and the strongest action taken on it (`added` / `deleted` / `renamed` outrank a plain `edited`). */
@@ -50,7 +59,9 @@ export const TurnFileChangeSchema = z.looseObject({ path: z.string(), action: z.
50
59
  /** How a turn ended: every terminal chat frame carries it, and the turn's settled scene shows the same value. */
51
60
  export const TurnTerminalStateSchema = z.enum(['completed', 'no_response', 'cancelled', 'interrupted', 'tool_limit_reached', 'compression_exhausted', 'error']);
52
61
  export const ActivitySceneSchema = z.looseObject({
53
- version: z.literal('activity_scene_v1'), activity_rows: z.array(ActivitySceneRowSchema), final_answer: z.string().optional(), turn_duration: z.number().nullable().optional(),
62
+ version: z.literal('activity_scene_v1'), activity_rows: z.array(ActivitySceneRowSchema), final_answer: z.string().optional(),
63
+ /** TAL-456: a settled final answer too long to lay out whole; clients render it collapsed, with a local "Show more". */
64
+ final_answer_excerpt: z.string().optional(), turn_duration: z.number().nullable().optional(),
54
65
  /** The turn's outcome (`completed`, `no_response`, `error`, `cancelled`, `interrupted`, `tool_limit_reached`, ...). */
55
66
  terminal_state: z.string().optional(),
56
67
  /** Whether the "Worked" disclosure opens by default: an unsuccessful outcome with work to read. */
@@ -63,6 +74,8 @@ export const ActivitySceneSchema = z.looseObject({
63
74
  file_changes: z.array(TurnFileChangeSchema).optional(),
64
75
  activity_rows_total: z.number().int().optional(), activity_rows_offset: z.number().int().optional(), activity_rows_complete: z.boolean().optional(), activity_rows_omitted: z.number().int().optional(), activity_scene_ref: z.string().optional(),
65
76
  });
77
+ /** TAL-460: one finished background item (see `_background_update.lines`). */
78
+ export const BackgroundLineSchema = z.object({ kind: z.enum(['agent', 'command', 'other']), status: z.enum(['completed', 'failed', 'notice']), label: z.string(), exit_code: z.number().int().nullable().optional() });
66
79
  export const MessageSchema = z.looseObject({
67
80
  role: z.string(), content: MessageContentSchema.optional(), id: MessageIdSchema.optional(), message_id: MessageIdSchema.optional(), timestamp: z.number().nullable().optional(),
68
81
  attachments: z.array(AttachmentSchema).optional(), tool_calls: z.array(ToolCallSchema).optional(), reasoning: z.union([z.string(), z.array(Json)]).nullable().optional(), reasoning_content: z.string().nullable().optional(),
@@ -76,9 +89,49 @@ export const MessageSchema = z.looseObject({
76
89
  _steer: z.looseObject({ steer_id: z.string(), submitted_at: z.number().nullable().optional(), consumed_at: z.number().optional(), phase_duration: z.number().optional() }).optional(),
77
90
  /** On a steered turn's last reply: seconds from the last consumed steer to the turn's end. */
78
91
  _final_phase_duration: z.number().optional(),
92
+ /** A settled body too long to lay out whole: clients render `_display_excerpt` collapsed, with a local "Show more" for `content`. */
93
+ _display_truncated: z.boolean().optional(),
94
+ _display_excerpt: z.string().optional(),
95
+ /**
96
+ * TAL-371: an automatic background wakeup (delegation results, background process or watch notice), not a message the
97
+ * user sent. Clients render it as a "Background update" disclosure: a localized label per `kind` (with `count`), a
98
+ * warning when `attention`, the server's one-line `summary`, and `content` in full on expansion.
99
+ */
100
+ _background_update: z.object({
101
+ kind: z.enum(['delegation', 'process', 'mixed', 'other']), attention: z.boolean(), count: z.number().int().positive(), summary: z.string(),
102
+ /**
103
+ * TAL-460: one completion line per finished item, shown in place of the row. Clients localize the wording per
104
+ * `kind` and `status`: an agent by its goal (`completed` / `failed`), a command (`finished` / `failed` with
105
+ * `exit_code`), or `label` as written for any other notice. `content` stays available in full on expansion.
106
+ */
107
+ lines: z.array(BackgroundLineSchema),
108
+ }).optional(),
109
+ /** TAL-460: part of the Agent's reply to the background update just before it in the same turn. */
110
+ _background_reply: z.boolean().optional(),
111
+ /** TAL-460: that reply is only a silence marker; clients show the update's lines and nothing of this turn's reply. */
112
+ _background_silent: z.boolean().optional(),
79
113
  });
80
114
  export const ComposerDraftSchema = z.looseObject({ text: z.string().optional(), files: z.array(Json).optional() });
115
+ /**
116
+ * TAL-424: a steer the Agent has not taken yet, owned by the server and shown by every client in order. `state`
117
+ * `sending_now`: a Send now is delivering it after the running tools yield. `actions` say what this client may offer.
118
+ */
119
+ export const PendingSteerSchema = z.object({
120
+ steer_id: z.string(), text: z.string(), submitted_at: z.number(), state: z.enum(['pending', 'sending_now']),
121
+ actions: z.object({ edit: z.boolean(), cancel: z.boolean(), send_now: z.boolean() }),
122
+ });
123
+ /**
124
+ * TAL-299: the context ring, computed by the server. `context_used_tokens` is the post-compression estimate, else the last
125
+ * prompt (never the cumulative `input_tokens`); `context_window_tokens` is the model's window. The percents are rounded
126
+ * and capped at 100. Each is null when unknown, and clients then show no percentage.
127
+ */
128
+ export const ContextUsageFields = {
129
+ context_used_tokens: z.number().int().positive().nullable().optional(), context_window_tokens: z.number().int().positive().nullable().optional(),
130
+ context_usage_percent: z.number().int().min(0).max(100).nullable().optional(), context_threshold_percent: z.number().int().min(0).max(100).nullable().optional(),
131
+ };
81
132
  // TAL-312: the server validates both flags on every session payload; clients render them and never re-derive them.
133
+ /** TAL-460: who started the running turn; `background` means a background result did, and the user's next message replaces it. */
134
+ const ActiveTurnOriginSchema = z.enum(['user', 'background']).nullable().optional().describe('Who started the running turn; null while idle.');
82
135
  const IsStreamingSchema = z.boolean().describe('True only while the session\'s run is a live stream on this server.');
83
136
  const ActiveStreamIdSchema = NullableString.optional().describe('The live stream id; non-null only while `is_streaming` is true.');
84
137
  const ReadOnlySchema = z.boolean().describe('The session cannot be modified from Web: a read-only import, a view-only subagent child, or a foreign session whose owner refuses claiming.');
@@ -86,16 +139,20 @@ const CanBranchSchema = z.boolean().describe('Web may branch this session: never
86
139
  const CanPinSchema = z.boolean().describe('Web may pin or unpin this session: never a subagent child.');
87
140
  const CanArchiveSchema = z.boolean().describe('Web may archive or unarchive this session: never a subagent child.');
88
141
  const CanDuplicateSchema = z.boolean().describe('Web may duplicate this session: it has a WebUI copy and is not a subagent child.');
142
+ // TAL-310: the server classifies every session's source once; clients file rows by it and never scan source markers.
143
+ export const SourceKindSchema = z.enum(['webui', 'cli', 'messaging', 'cron', 'webhook', 'subagent', 'claude_code', 'kanban', 'api', 'other'])
144
+ .describe('The session\'s source family. `is_cli_session` is true only for `cli` and `claude_code`.');
145
+ const IsMessagingSessionSchema = z.boolean().describe('`source_kind` is `messaging`: a gateway chat (Telegram, Signal, WhatsApp, …) the server imports before Web continues it.');
89
146
  /** Full session record from `GET /api/session` and mutations returning `session`. */
90
147
  export const SessionSchema = z.looseObject({
91
148
  session_id: SessionIdSchema, title: z.string(), workspace: z.string().optional(), created_workspace: z.string().nullable().optional(), model: NullableString.optional(), model_provider: NullableString.optional(),
92
149
  messages: z.array(MessageSchema).optional(), tool_calls: z.array(ToolCallSchema).optional(), created_at: UnixSeconds.optional(), updated_at: UnixSeconds.optional(), last_message_at: NullableNumber.optional(),
93
150
  message_count: z.number().optional(), user_message_count: z.number().optional(), pinned: z.boolean().optional(), archived: z.boolean().optional(), project_id: NullableString.optional(), profile: NullableString.optional(),
94
151
  personality: NullableString.optional(), input_tokens: z.number().optional(), output_tokens: z.number().optional(), cache_read_tokens: z.number().optional(), cache_write_tokens: z.number().optional(),
95
- cache_hit_percent: NullableNumber.optional(), estimated_cost: NullableNumber.optional(), active_stream_id: ActiveStreamIdSchema, is_streaming: IsStreamingSchema, has_pending_user_message: z.boolean().optional(),
152
+ cache_hit_percent: NullableNumber.optional(), estimated_cost: NullableNumber.optional(), active_stream_id: ActiveStreamIdSchema, is_streaming: IsStreamingSchema, active_turn_origin: ActiveTurnOriginSchema, pending_steers: z.array(PendingSteerSchema).optional().describe('TAL-424: the active stream\'s pending steers, oldest first.'), has_pending_user_message: z.boolean().optional(),
96
153
  pending_user_message: NullableString.optional(), pending_attachments: z.array(AttachmentSchema).optional(), pending_started_at: NullableNumber.optional(), pending_user_source: NullableString.optional(),
97
- context_length: NullableNumber.optional(), threshold_tokens: NullableNumber.optional(), last_prompt_tokens: NullableNumber.optional(), post_compression_context_tokens_estimate: NullableNumber.optional(),
98
- enabled_toolsets: z.array(z.string()).nullable().optional(), composer_draft: ComposerDraftSchema.optional(), is_cli_session: z.boolean().optional(), read_only: ReadOnlySchema, can_branch: CanBranchSchema, can_pin: CanPinSchema, can_archive: CanArchiveSchema, can_duplicate: CanDuplicateSchema, source_tag: NullableString.optional(),
154
+ context_length: NullableNumber.optional(), threshold_tokens: NullableNumber.optional(), last_prompt_tokens: NullableNumber.optional(), post_compression_context_tokens_estimate: NullableNumber.optional(), ...ContextUsageFields,
155
+ enabled_toolsets: z.array(z.string()).nullable().optional(), composer_draft: ComposerDraftSchema.optional(), is_cli_session: z.boolean().optional(), source_kind: SourceKindSchema, is_messaging_session: IsMessagingSessionSchema, read_only: ReadOnlySchema, can_branch: CanBranchSchema, can_pin: CanPinSchema, can_archive: CanArchiveSchema, can_duplicate: CanDuplicateSchema, source_tag: NullableString.optional(),
99
156
  source_label: NullableString.optional(), session_source: NullableString.optional(), raw_source: NullableString.optional(), parent_session_id: NullableString.optional(), worktree_path: NullableString.optional(),
100
157
  worktree_branch: NullableString.optional(), worktree_repo_root: NullableString.optional(), share_token: NullableString.optional(), share_created_at: NullableNumber.optional(), manual_title: z.boolean().optional(),
101
158
  compression_anchor_summary: NullableString.optional(), compression_recovery: z.record(z.string(), Json).optional(), recommended_recovery_action: NullableString.optional(), compression_recovery_action: NullableString.optional(),
@@ -106,23 +163,30 @@ export const SessionSchema = z.looseObject({
106
163
  * `messages` are the whole persisted transcript and a client attaches live without replay.
107
164
  */
108
165
  transcript_seq: z.object({ stream_id: z.string(), seq: z.number().int().nonnegative() }).nullable().optional(),
166
+ /** The agent's display name: a named profile's own name, else the `bot_name` setting (TAL-458). */
167
+ assistant_name: z.string().optional(),
109
168
  });
110
169
  export const SessionEnvelopeSchema = z.looseObject({ session: SessionSchema });
111
170
  /** Sidebar row from `GET /api/sessions`. */
112
171
  export const SessionRowSchema = z.looseObject({
113
172
  session_id: SessionIdSchema, title: z.string(), workspace: z.string().optional(), model: NullableString.optional(), created_at: UnixSeconds.optional(), updated_at: UnixSeconds.optional(), last_message_at: NullableNumber.optional(),
114
173
  message_count: z.number().optional(), pinned: z.boolean().optional(), archived: z.boolean().optional(), project_id: NullableString.optional(), profile: NullableString.optional(), is_streaming: IsStreamingSchema,
115
- is_cli_session: z.boolean().optional(), cron_running: z.boolean().optional(), read_only: ReadOnlySchema, can_branch: CanBranchSchema, can_pin: CanPinSchema, can_archive: CanArchiveSchema, can_duplicate: CanDuplicateSchema, attention: z.looseObject({ kind: z.string().optional(), count: z.number().optional() }).nullable().optional(),
174
+ is_cli_session: z.boolean().optional(), source_kind: SourceKindSchema, is_messaging_session: IsMessagingSessionSchema, cron_running: z.boolean().optional(), read_only: ReadOnlySchema, can_branch: CanBranchSchema, can_pin: CanPinSchema, can_archive: CanArchiveSchema, can_duplicate: CanDuplicateSchema, attention: z.looseObject({ kind: z.string().optional(), count: z.number().optional() }).nullable().optional(),
116
175
  source_tag: NullableString.optional(), source_label: NullableString.optional(), session_source: NullableString.optional(), raw_source: NullableString.optional(), parent_session_id: NullableString.optional(),
117
176
  active_stream_id: ActiveStreamIdSchema, share_token: NullableString.optional(), worktree_branch: NullableString.optional(), match_type: z.string().optional(), match_preview: NullableString.optional(),
177
+ ...ContextUsageFields,
118
178
  });
119
179
  export const SessionsListSchema = z.looseObject({
120
180
  sessions: z.array(SessionRowSchema), sidebar_reference_sessions: z.array(SessionRowSchema), server_time: z.number(), server_tz: z.string(), active_profile: z.string(), all_profiles: z.boolean(), include_archived: z.boolean(),
121
181
  archived_count: z.number().int(), archived_webui_count: z.number().int(), archived_cli_count: z.number().int(), other_profile_count: z.number().int(), cli_count: z.number().int(), webui_session_count: z.number().int(),
122
- cli_session_count: z.number().int(), archived_limit: z.number().int().nullable().optional(), archived_offset: z.number().int().optional(),
182
+ cli_session_count: z.number().int(),
183
+ // TAL-482: sidebar counts for the "Scheduled sessions" / "Webhook sessions" groups; `_truncated` means more exist than are listed.
184
+ scheduled_session_count: z.number().int().nonnegative(), scheduled_sessions_truncated: z.boolean(),
185
+ webhook_session_count: z.number().int().nonnegative(), webhook_sessions_truncated: z.boolean(),
186
+ archived_limit: z.number().int().nullable().optional(), archived_offset: z.number().int().optional(),
123
187
  });
124
188
  export const SessionStatusSchema = z.looseObject({
125
- session_id: SessionIdSchema, title: z.string().optional(), active_stream_id: ActiveStreamIdSchema, agent_running: IsStreamingSchema, is_streaming: IsStreamingSchema, read_only: ReadOnlySchema, message_count: z.number().optional(), model: NullableString.optional(),
189
+ session_id: SessionIdSchema, title: z.string().optional(), active_stream_id: ActiveStreamIdSchema, active_turn_origin: ActiveTurnOriginSchema, agent_running: IsStreamingSchema, is_streaming: IsStreamingSchema, read_only: ReadOnlySchema, message_count: z.number().optional(), model: NullableString.optional(),
126
190
  profile: NullableString.optional(), workspace: z.string().optional(), input_tokens: z.number().optional(), output_tokens: z.number().optional(), total_tokens: z.number().optional(), estimated_cost: NullableNumber.optional(), updated_at: UnixSeconds.optional(),
127
191
  });
128
192
  export const SessionUsageSchema = z.looseObject({ input_tokens: z.number().optional(), output_tokens: z.number().optional(), total_tokens: z.number().optional(), estimated_cost: NullableNumber.optional(), model: NullableString.optional() });
@@ -140,11 +204,28 @@ export const ChatStartResponseSchema = z.looseObject({
140
204
  title: z.string().optional(), effective_model: z.string().optional(), effective_model_provider: z.string().optional(), queued: z.boolean().optional(), ok: z.boolean().optional(), status: z.string().optional(), reason: z.string().optional(), session: SessionSchema.optional(),
141
205
  });
142
206
  export const StreamStatusSchema = z.looseObject({ active: z.boolean(), stream_id: z.string(), replay_available: z.boolean().optional(), journal: Json.optional() });
143
- export const CancelResponseSchema = z.looseObject({ ok: z.boolean(), cancelled: z.boolean(), stream_id: z.string().optional(), error: z.string().optional() });
207
+ /**
208
+ * TAL-424: a pending steer is no longer pending without the Agent taking it: Edit and Cancel by the user, `stopped` by a
209
+ * Stop (`text` goes back to the composer), or `followup` when the server sends it as the next turn. `steer_id` is null
210
+ * for text another surface queued with the Agent.
211
+ */
212
+ export const SteerWithdrawnSchema = z.object({ steer_id: z.string().nullable(), reason: z.enum(['edit', 'cancel', 'stopped', 'followup']), text: z.string() });
213
+ /**
214
+ * `withdrawn_steers` (TAL-426): the steers this Stop withdrew, the same `steer_withdrawn` (stopped) the stream carries, so
215
+ * a client that stops reading the stream once the Stop answers still gives its own text back.
216
+ */
217
+ export const CancelResponseSchema = z.looseObject({ ok: z.boolean(), cancelled: z.boolean(), stream_id: z.string().optional(), withdrawn_steers: z.array(SteerWithdrawnSchema).optional(), error: z.string().optional() });
218
+ export const SteerWithdrawRequestSchema = z.object({ session_id: SessionIdSchema, steer_id: z.string().min(1), reason: z.enum(['edit', 'cancel']) });
219
+ /** `withdrawn: false` when the steer is unknown, already taken by the Agent (it then settles as consumed), or being sent. */
220
+ export const SteerWithdrawResponseSchema = z.object({ withdrawn: z.boolean(), text: z.string().optional() });
221
+ export const SteerSendNowRequestSchema = z.object({ session_id: SessionIdSchema, steer_id: z.string().min(1) });
222
+ /** `redirected: false` when nothing is live to deliver it to now; the steer stays pending. */
223
+ export const SteerSendNowResponseSchema = z.object({ redirected: z.boolean() });
144
224
  /** `text` is delivered to the running agent; `display_text` is what the transcript shows. */
145
225
  export const SteerRequestSchema = z.looseObject({ session_id: SessionIdSchema, text: z.string().min(1), display_text: z.string().optional(), steer_id: z.string().optional() });
146
226
  /** `accepted: false` with a `fallback` reason means the message was not delivered; the caller keeps the draft. */
147
- export const SteerResponseSchema = z.looseObject({ accepted: z.boolean(), fallback: NullableString.optional(), stream_id: NullableString.optional(), steer_id: z.string().optional() });
227
+ /** TAL-460: a steer sent while a background turn runs starts the user's own turn instead; `started_turn` is its start response. */
228
+ export const SteerResponseSchema = z.looseObject({ accepted: z.boolean(), fallback: NullableString.optional(), stream_id: NullableString.optional(), steer_id: z.string().optional(), started_turn: ChatStartResponseSchema.optional() });
148
229
  export const ApprovalPendingSchema = z.looseObject({
149
230
  approval_id: z.string().optional(), session_id: z.string().optional(), command: z.string().optional(), description: z.string().optional(), title: z.string().optional(), name: z.string().optional(), kind: z.string().optional(),
150
231
  reason: z.string().optional(), action: z.string().optional(), question: z.string().optional(), status: z.string().optional(), pending_count: z.number().optional(), run_id: z.string().optional(), mirror_token: z.string().optional(),
@@ -174,6 +255,42 @@ export const GoalViewSchema = z.looseObject({ text: z.string().optional(), state
174
255
  export const GoalResponseSchema = z.looseObject({ ok: z.boolean().optional(), action: z.string().optional(), goal: GoalViewSchema.nullable().optional(), message: z.string().optional(), message_key: z.string().optional(), stream_id: z.string().optional(), status: z.string().optional(), reason: z.string().optional() });
175
256
  export const BackgroundResultSchema = z.looseObject({ id: z.string().optional(), task_id: z.string().optional(), status: z.string().optional(), title: z.string().optional(), summary: z.string().optional(), prompt: z.string().optional(), answer: NullableString.optional(), error: z.string().optional(), completed_at: z.number().nullable().optional() });
176
257
  export const BackgroundStatusSchema = z.looseObject({ results: z.array(BackgroundResultSchema) });
258
+ /**
259
+ * TAL-372: one piece of background work a session owns, the same record for every client, reload and restart.
260
+ * - `task_id` is stable: the delegation unit's id, the process id, or the `/background` task id.
261
+ * - `status` `attention` is a stalled agent or a matched watch; `unknown` is running work the Agent cannot confirm now.
262
+ * - `title` is the goal, command or prompt (one line, never output); `agents` counts a delegation's subagents.
263
+ * - `result_available` means `GET /api/background/result` returns its full result.
264
+ * - `pinned` puts it in the chat's background tray: running work, and a finished `/background` result until dismissed.
265
+ * - `dismissible` offers Dismiss: a finished `/background` result, or work nobody can confirm (`unknown`).
266
+ * - `active`: not settled yet (running, attention or unknown); clients keep refreshing while any record is active.
267
+ */
268
+ export const BackgroundTaskSchema = z.object({
269
+ task_id: z.string(),
270
+ kind: z.enum(['delegation', 'process', 'background_command']),
271
+ status: BackgroundLinkSchema.shape.status,
272
+ title: z.string(),
273
+ started_at: z.number().nullable(),
274
+ updated_at: z.number(),
275
+ completed_at: z.number().nullable(),
276
+ result_available: z.boolean(),
277
+ /** TAL-494: each subagent session a delegation ran, by its goal: a read-only transcript to open. */
278
+ child_sessions: z.array(z.object({ goal: z.string(), session_id: SessionIdSchema })),
279
+ exit_code: z.number().int().nullable(),
280
+ agents: BackgroundLinkSchema.shape.agents.nullable(),
281
+ pinned: z.boolean(),
282
+ dismissible: z.boolean(),
283
+ active: z.boolean(),
284
+ });
285
+ /**
286
+ * `agent_available: false` when the Agent could not be asked: running work then shows `unknown`. Reading never consumes a
287
+ * result. `agents_working` (TAL-373): a delegation is running or needs attention, whatever `kind` the list is narrowed to;
288
+ * the chat's side panel opens on Agents for it. Absent from servers before TAL-373.
289
+ */
290
+ export const BackgroundTasksResponseSchema = z.object({ session_id: z.string(), tasks: z.array(BackgroundTaskSchema), agent_available: z.boolean(), agents_working: z.boolean().optional() });
291
+ export const BackgroundTaskResultSchema = z.object({ task_id: z.string(), text: z.string() });
292
+ export const BackgroundDismissRequestSchema = z.object({ session_id: SessionIdSchema, task_id: z.string().min(1) });
293
+ export const BackgroundDismissResponseSchema = z.object({ ok: z.literal(true), task: BackgroundTaskSchema });
177
294
  export const ShareCreateResponseSchema = z.looseObject({ ok: z.literal(true), share: z.looseObject({ token: z.string(), url: z.string(), title: z.string(), message_count: z.number().int(), created_at: z.number(), updated_at: z.number() }), session: SessionRowSchema });
178
295
  export const ShareMessageSchema = z.looseObject({ role: z.string(), content: z.union([z.string(), z.null(), z.array(Json)]).optional() });
179
296
  export const ShareSchema = z.looseObject({ title: z.string(), messages: z.array(ShareMessageSchema), message_count: z.number().int(), created_at: z.number().optional(), updated_at: z.number().optional(), model: NullableString.optional() });
@@ -187,7 +304,7 @@ export const TranscribeCapabilitySchema = z.looseObject({ ok: z.literal(true), a
187
304
  export const LoginResponseSchema = z.looseObject({ ok: z.literal(true), message: z.string().optional() });
188
305
  export const SettingsSchema = z.looseObject({
189
306
  bot_name: z.string().optional(), default_model: z.string().optional(), default_workspace: z.string().optional(), language: z.string().optional(), send_key: z.string().optional(), font_size: z.string().optional(),
190
- full_width_chat: z.boolean().optional(), auto_scroll_follow: z.boolean().optional(), render_user_markdown: z.boolean().optional(), chat_activity_display_mode: z.string().optional(), default_message_mode: z.string().optional(),
307
+ full_width_chat: z.boolean().optional(), chat_width: z.enum(['comfortable', 'wide', 'full']).optional(), auto_scroll_follow: z.boolean().optional(), render_user_markdown: z.boolean().optional(), chat_activity_display_mode: z.string().optional(), default_message_mode: z.string().optional(),
191
308
  fade_text_effect: z.boolean().optional(), hidden_tabs: z.array(z.string()).optional(), composer_control_order: z.array(z.string()).optional(), show_cli_sessions: z.boolean().optional(), show_claude_code_sessions: z.boolean().optional(),
192
309
  show_cron_sessions: z.boolean().optional(), show_webhook_sessions: z.boolean().optional(), show_kanban_sessions: z.boolean().optional(), check_for_updates: z.boolean().optional(), ignore_agent_updates: z.boolean().optional(),
193
310
  auth_enabled: z.boolean().optional(), password_auth_enabled: z.boolean().optional(), password_env_var: z.boolean().optional(), passkeys_enabled: z.boolean().optional(), passwordless_enabled: z.boolean().optional(),
@@ -219,8 +336,22 @@ export const QuotaSourceSchema = z.looseObject({
219
336
  /** Python `get_provider_quotas`: the stable identity envelope the iOS quota widget persists (`scope_id`/`profile_id`). */
220
337
  export const ProviderQuotasSchema = z.looseObject({ version: z.number(), scope_id: z.string(), profile_id: z.string(), active_provider: NullableString, requested_source_id: NullableString, missing_source: z.boolean(), sources: z.array(QuotaSourceSchema) });
221
338
  export const PersonalitiesSchema = z.looseObject({ personalities: z.array(z.looseObject({ name: z.string(), description: z.string().optional() })) });
222
- export const AuxiliaryTaskSchema = z.looseObject({ task: z.string(), label: z.string().optional(), description: z.string().optional(), model: z.string().optional(), provider: z.string().optional(), base_url: z.string().optional(), api_key_set: z.boolean().optional() });
223
- export const AuxiliaryModelsSchema = z.looseObject({ main: z.looseObject({ model: z.string().optional(), provider: z.string().optional(), base_url: z.string().optional(), api_key_set: z.boolean().optional() }).optional(), tasks: z.array(AuxiliaryTaskSchema).optional() });
339
+ /** One auxiliary task slot in server order (TAL-388). The server owns ordering, normalization, and catalog matching. */
340
+ export const AuxiliaryTaskSchema = z.looseObject({
341
+ task: z.string(), label: z.string(), description: z.string(),
342
+ /** Saved provider (`auto` when unset) and bare model id. */
343
+ provider: z.string(), model: z.string(),
344
+ base_url: z.string().optional(), api_key_set: z.boolean().optional(),
345
+ /** No override is saved: the task falls back to the main chat model. */
346
+ is_auto: z.boolean(),
347
+ /** Display of the model the task uses (the main chat model when `is_auto`): catalog label, else the saved model id, and its provider name. */
348
+ value_label: z.string().nullable(), provider_label: z.string().nullable(),
349
+ /** The `/api/models` entry id matching the saved provider/model pair; null for Auto or a model absent from the catalog. */
350
+ selected_option_id: z.string().nullable(),
351
+ /** False only for a pinned model the current catalog does not list; it stays visible and editable. */
352
+ in_catalog: z.boolean(),
353
+ });
354
+ export const AuxiliaryModelsSchema = z.looseObject({ main: z.looseObject({ model: z.string().optional(), provider: z.string().optional(), base_url: z.string().optional(), api_key_set: z.boolean().optional() }), tasks: z.array(AuxiliaryTaskSchema) });
224
355
  export const MaxTokensSchema = z.looseObject({ max_tokens: NullableNumber, max_tokens_effective: NullableNumber, max_tokens_fallback: NullableNumber });
225
356
  // ── workspaces, files, git ───────────────────────────────────────────────
226
357
  export const WorkspaceEntrySchema = z.looseObject({ name: z.string().optional(), path: z.string() });
@@ -344,6 +475,7 @@ export const OnboardingOAuthSchema = z.looseObject({ ok: z.boolean().optional(),
344
475
  // ── automation ───────────────────────────────────────────────────────────
345
476
  export const CronScheduleSchema = z.looseObject({ kind: z.string().optional(), expr: z.string().optional(), minutes: z.number().optional(), run_at: z.string().optional(), display: z.string().optional() });
346
477
  export const CronRepeatSchema = z.looseObject({ times: NullableNumber.optional(), completed: z.number().optional() });
478
+ export const CronDerivedStateSchema = z.enum(['needs_attention', 'schedule_error', 'paused', 'off', 'error', 'active']);
347
479
  export const CronJobViewSchema = z.looseObject({
348
480
  read_only: z.boolean().optional(), owner_profile: NullableString.optional(), id: z.string().optional(), job_id: z.string().optional(), name: z.string().nullable().optional(), prompt: z.string().optional(), schedule: z.union([z.string(), CronScheduleSchema]).optional(),
349
481
  schedule_display: z.string().optional(), enabled: z.boolean().optional(), paused: z.boolean().optional(), paused_reason: NullableString.optional(), state: NullableString.optional(), last_status: NullableString.optional(), last_error: NullableString.optional(),
@@ -351,8 +483,17 @@ export const CronJobViewSchema = z.looseObject({
351
483
  status: z.string().optional(), last_run: Json.optional(), next_run: Json.optional(), profile: NullableString.optional(), session_id: NullableString.optional(), model: NullableString.optional(), provider: NullableString.optional(), workspace: NullableString.optional(),
352
484
  workdir: NullableString.optional(), deliver: NullableString.optional(), skills: z.array(z.string()).optional(), no_agent: z.boolean().optional(), script: NullableString.optional(), monitor: NullableString.optional(), continuity: z.boolean().optional(),
353
485
  context_from: z.union([z.array(z.string()), z.string(), z.null()]).optional(), reasoning_effort: NullableString.optional(), toast_notifications: z.boolean().optional(), running: z.boolean().optional(),
486
+ /** Server-derived (TAL-296): the job's status, whether it needs attention, and whether its primary action is Resume. Live runs overlay it. */
487
+ derived_state: CronDerivedStateSchema.optional(), needs_attention: z.boolean().optional(), resumable: z.boolean().optional(),
354
488
  });
355
489
  export const CronsSchema = z.looseObject({ jobs: z.array(CronJobViewSchema), active_profile: z.string().optional(), all_profiles: z.boolean().optional(), other_profile_count: z.number().optional(), cron_unavailable: z.boolean().optional() });
490
+ export const CronContextSourcesSchema = z.object({ profile: z.string(), sources: z.array(z.object({ job_id: z.string(), label: z.string(), selectable: z.boolean() })) });
491
+ /** `GET /api/crons/recent` row: one job's latest completion, ordered and classified by the server. */
492
+ export const CronRecentCompletionSchema = z.object({
493
+ job_id: z.string(), name: z.string().nullable(), status: z.string().nullable(), outcome: z.enum(['succeeded', 'failed', 'unknown']), completed_at: z.number(),
494
+ toast_notifications: z.boolean(), session_id: z.string(), message_count: z.number().int().optional(),
495
+ });
496
+ export const CronRecentSchema = z.object({ completions: z.array(CronRecentCompletionSchema), since: z.number() });
356
497
  export const CronMutationSchema = z.looseObject({ ok: z.boolean().optional(), job: CronJobViewSchema.optional(), job_id: z.string().optional(), status: z.string().optional(), error: z.string().optional(), elapsed: z.number().optional() });
357
498
  export const CronRunUsageSchema = z.looseObject({ input_tokens: NullableNumber.optional(), output_tokens: NullableNumber.optional(), total_tokens: NullableNumber.optional(), estimated_cost_usd: NullableNumber.optional(), duration_seconds: NullableNumber.optional(), model: z.string().optional(), provider: z.string().optional() });
358
499
  export const CronRunSummarySchema = z.looseObject({ filename: z.string(), size: z.number(), modified: z.number(), usage: CronRunUsageSchema.optional() });