@maudecode/talaria-web-contracts 1.15.0 → 1.17.2

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.
Files changed (47) hide show
  1. package/dist/router.d.ts +1681 -87
  2. package/dist/routes/automation.d.ts +168 -16
  3. package/dist/routes/automation.js +15 -8
  4. package/dist/routes/automation.js.map +1 -1
  5. package/dist/routes/chat.d.ts +193 -6
  6. package/dist/routes/core.d.ts +8 -4
  7. package/dist/routes/core.js +2 -0
  8. package/dist/routes/core.js.map +1 -1
  9. package/dist/routes/git.d.ts +22 -1
  10. package/dist/routes/git.js +7 -3
  11. package/dist/routes/git.js.map +1 -1
  12. package/dist/routes/raw.js +2 -2
  13. package/dist/routes/raw.js.map +1 -1
  14. package/dist/routes/sessions.d.ts +1187 -43
  15. package/dist/routes/sessions.js +22 -3
  16. package/dist/routes/sessions.js.map +1 -1
  17. package/dist/routes/settings.d.ts +254 -11
  18. package/dist/routes/settings.js +9 -8
  19. package/dist/routes/settings.js.map +1 -1
  20. package/dist/routes/tools.d.ts +86 -9
  21. package/dist/routes/tools.js +1 -1
  22. package/dist/routes/tools.js.map +1 -1
  23. package/dist/routes/workspaces.d.ts +19 -5
  24. package/dist/sidecar/methods.d.ts +192 -12
  25. package/dist/sidecar/methods.js +2 -1
  26. package/dist/sidecar/methods.js.map +1 -1
  27. package/dist/sidecar/namespaces.d.ts +315 -15
  28. package/dist/sidecar/namespaces.js +78 -9
  29. package/dist/sidecar/namespaces.js.map +1 -1
  30. package/dist/sidecar/version.d.ts +1 -1
  31. package/dist/sidecar/version.js +1 -1
  32. package/dist/sidecar/version.js.map +1 -1
  33. package/dist/sse.d.ts +68 -9
  34. package/dist/sse.js +21 -7
  35. package/dist/sse.js.map +1 -1
  36. package/dist/views.d.ts +1392 -40
  37. package/dist/views.js +242 -37
  38. package/dist/views.js.map +1 -1
  39. package/fixtures/sidecar/commands.json +195 -92
  40. package/fixtures/sidecar/kanban.json +24 -8
  41. package/fixtures/sidecar/plugins.json +1 -403
  42. package/fixtures/sidecar/process.json +25 -0
  43. package/fixtures/sidecar/rpc.json +2 -1
  44. package/fixtures/sidecar/runtime.json +3 -3
  45. package/fixtures/sidecar/text.json +0 -10
  46. package/fixtures/sidecar/usage.json +51 -0
  47. package/package.json +1 -1
package/dist/views.js CHANGED
@@ -17,19 +17,32 @@ export const AttachmentSchema = z.looseObject({
17
17
  });
18
18
  /** The server's display class for a tool call: clients map it to an icon and localized verb, and never classify names themselves. */
19
19
  export const ToolKindSchema = z.enum(['shell', 'read', 'list', 'search', 'web', 'write', 'skill', 'memory', 'delegate', 'unknown']);
20
- /** Server-derived display fields every tool call carries: the kind, and the redacted first-line label of its main argument (`''` when none; omitted on a live frame with none, which keeps its start frame's target). */
21
- export const ToolDisplayFields = { kind: ToolKindSchema.optional(), target: z.string().optional() };
20
+ /**
21
+ * TAL-448: a completed file-edit call's change, from its result's unified diff: `added` / `removed` count the whole diff's
22
+ * lines (file headers excluded); `diff` is the redacted diff, capped at 400 lines, with `truncated` when cut. Absent on a
23
+ * call whose result has no diff.
24
+ */
25
+ export const ToolEditDiffSchema = z.object({ added: z.number().int(), removed: z.number().int(), diff: z.string(), truncated: z.boolean() });
26
+ /** Server-derived display fields every tool call carries: the kind, and the redacted first-line label of its main argument (`''` when none; omitted on a live frame with none, which keeps its start frame's target), and a file edit's `edit_diff`. */
27
+ export const ToolDisplayFields = { kind: ToolKindSchema.optional(), target: z.string().optional(), edit_diff: ToolEditDiffSchema.optional() };
28
+ /**
29
+ * TAL-315: a tool result's display sections, decided by the server's one rule for live, replayed and persisted results
30
+ * alike. Clients show `stdout`, `stderr`, a labelled `error` and a labelled `exit_code` (sent only when worth showing), or
31
+ * `text`, in that order, and never parse result text. Each string is capped server-side; a client without the field
32
+ * shows `preview`.
33
+ */
34
+ export const ToolResultViewSchema = z.object({ text: z.string().optional(), stdout: z.string().optional(), stderr: z.string().optional(), error: z.string().optional(), exit_code: z.number().int().optional() });
22
35
  /**
23
36
  * Stored transcripts carry the OpenAI shape (`function.name` / `function.arguments` JSON string); live events carry `name` / `args`.
24
37
  * On an assistant row the server resolves every call (TAL-313, `session-sse-contract-v1.md`): Anthropic `tool_use` parts and
25
38
  * session-level-only calls join `tool_calls`; `done` is answered or outside the running turn; `is_error` is the server's
26
39
  * outcome rule over the result; `duration` is the seconds the live stream measured, else `null`; `result` is the redacted
27
- * result snippet, else `null`. Clients render these and pair nothing themselves.
40
+ * result snippet, else `null`; `result_view` its display sections, else `null`. Clients render these and pair nothing themselves.
28
41
  */
29
42
  export const ToolCallSchema = z.looseObject({
30
43
  ...ToolDisplayFields,
31
44
  name: z.string().optional(), args: Json.optional(), function: z.looseObject({ name: z.string().optional(), arguments: Json.optional() }).optional(), id: z.string().optional(), call_id: z.string().optional(), tool_call_id: z.string().optional(),
32
- done: z.boolean().optional(), is_error: z.boolean().optional(), preview: z.string().nullable().optional(), result: Json.optional(), output: Json.optional(), duration: z.number().nullable().optional(), cost_usd: z.number().nullable().optional(),
45
+ done: z.boolean().optional(), is_error: z.boolean().optional(), preview: z.string().nullable().optional(), result: Json.optional(), result_view: ToolResultViewSchema.nullable().optional(), output: Json.optional(), duration: z.number().nullable().optional(), cost_usd: z.number().nullable().optional(),
33
46
  timestamp: z.number().nullable().optional(), event_type: z.string().optional(),
34
47
  });
35
48
  export const ContentPartSchema = z.looseObject({ type: z.string(), text: z.string().optional() });
@@ -46,11 +59,23 @@ export const BackgroundLinkSchema = z.object({
46
59
  status: z.enum(['running', 'attention', 'completed', 'failed', 'cancelled', 'unknown']),
47
60
  agents: z.object({ total: z.number().int(), completed: z.number().int(), failed: z.number().int(), running: z.number().int() }),
48
61
  });
62
+ /**
63
+ * TAL-186: one local or remote media reference the server recognized in a message's Markdown (a `MEDIA:` token, a bare
64
+ * `file://` URL or a local image destination), in content order. `url` is relative to the app root (`./api/media?path=…&session_id=…`)
65
+ * for a local file the `/api/media` allow-list serves, or the remote `http(s)` URL as written. Clients render `image`
66
+ * items inline from the display text and every other kind as a tile after it. A remote URL without a file
67
+ * extension has no known kind and is a `file`.
68
+ */
69
+ export const DisplayMediaSchema = z.object({ url: z.string(), name: z.string(), mime: z.string(), kind: z.enum(['image', 'audio', 'video', 'pdf', 'file']) });
49
70
  /** One normalized activity row: the server decides role, order, tool completion/error, and steering consumption. */
50
71
  export const ActivitySceneRowSchema = z.looseObject({
51
72
  row_id: z.string(), order_index: z.number().int(), role: z.enum(['prose', 'reasoning', 'tool', 'steering']), created_at: z.number().optional(),
52
73
  text: z.string().optional(), titles: z.array(z.string()).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(),
74
+ /** TAL-186: a prose row's `text` with its media references rewritten for display (see `_display_content`), and its media. */
75
+ display_text: z.string().optional(), media: z.array(DisplayMediaSchema).optional(),
76
+ tool: z.looseObject({ id: z.string(), name: z.string(), ...ToolDisplayFields, args: Json.optional(), preview: z.string().nullable(), result: Json.optional(), result_view: ToolResultViewSchema.nullable().optional(), done: z.boolean(), is_error: z.boolean(), duration: z.number().nullable(), cost_usd: z.number().nullable(), background: BackgroundLinkSchema.optional(),
77
+ /** TAL-331: a limited response clipped `result` (originally `result_chars` long); `GET /api/session/tool-result` serves it whole. */
78
+ result_truncated: z.literal(true).optional(), result_chars: z.number().int().nullable().optional() }).optional(),
54
79
  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(),
55
80
  });
56
81
  /** 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`). */
@@ -62,9 +87,10 @@ export const ActivitySceneSchema = z.looseObject({
62
87
  version: z.literal('activity_scene_v1'), activity_rows: z.array(ActivitySceneRowSchema), final_answer: z.string().optional(),
63
88
  /** TAL-456: a settled final answer too long to lay out whole; clients render it collapsed, with a local "Show more". */
64
89
  final_answer_excerpt: z.string().optional(), turn_duration: z.number().nullable().optional(),
65
- /** The turn's outcome (`completed`, `no_response`, `error`, `cancelled`, `interrupted`, `tool_limit_reached`, ...). */
66
- terminal_state: z.string().optional(),
67
- /** Whether the "Worked" disclosure opens by default: an unsuccessful outcome with work to read. */
90
+ /** TAL-186: `final_answer` with its media references rewritten for display (see `_display_content`), and its media. */
91
+ final_answer_display: z.string().optional(), final_answer_media: z.array(DisplayMediaSchema).optional(),
92
+ terminal_state: z.string().optional().describe('The turn\'s outcome: a `TurnTerminalState` (`completed`, `no_response`, `error`, `cancelled`, `interrupted`, `tool_limit_reached`, ...), or `running` for the running turn of a run with no journal to replay (TAL-374). A running scene holds every persisted row (no final answer) and renders open, with no "Worked" fold or outcome; the live rows streamed after attach continue it, and the settled scene replaces both.'),
93
+ /** Whether the "Worked" disclosure opens by default: an unsuccessful outcome with work to read, or a running turn. */
68
94
  expanded_by_default: z.boolean().optional(),
69
95
  /** Seconds from the turn's last consumed steer to its end, when it has steers. */
70
96
  final_phase_duration: z.number().optional(),
@@ -78,8 +104,14 @@ export const ActivitySceneSchema = z.looseObject({
78
104
  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() });
79
105
  export const MessageSchema = z.looseObject({
80
106
  role: z.string(), content: MessageContentSchema.optional(), id: MessageIdSchema.optional(), message_id: MessageIdSchema.optional(), timestamp: z.number().nullable().optional(),
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(),
82
- thinking: z.string().nullable().optional(), tool_call_id: z.string().optional(), tool_use_id: z.string().optional(), name: z.string().optional(), badge: z.string().optional(), label: z.string().optional(),
107
+ attachments: z.array(AttachmentSchema).optional(), tool_calls: z.array(ToolCallSchema).optional(),
108
+ /**
109
+ * TAL-302: an assistant row's whole reasoning as one string (its reasoning fields, typed thinking parts and inline
110
+ * thinking blocks). Its `content` carries none of them and no leaked tool-call XML; `reasoning_content` and
111
+ * `thinking` are not sent. Clients render both as shipped.
112
+ */
113
+ reasoning: z.string().optional(),
114
+ tool_call_id: z.string().optional(), tool_use_id: z.string().optional(), name: z.string().optional(), badge: z.string().optional(), label: z.string().optional(),
83
115
  provider_details: Json.optional(), provider_details_label: z.string().optional(), recovery_control: Json.optional(), _anchor_activity_scene: ActivitySceneSchema.optional(),
84
116
  /** The turn this row belongs to; the server stamps every row it sends, so clients group turns by equality alone. */
85
117
  _turn_id: z.string().optional(),
@@ -92,6 +124,19 @@ export const MessageSchema = z.looseObject({
92
124
  /** A settled body too long to lay out whole: clients render `_display_excerpt` collapsed, with a local "Show more" for `content`. */
93
125
  _display_truncated: z.boolean().optional(),
94
126
  _display_excerpt: z.string().optional(),
127
+ /**
128
+ * TAL-452: a typed user message (never a steer, background update or marker) longer than 20 lines or 2,000
129
+ * characters. Clients show it folded to a few lines with a local "Show more"; copy and edit use the whole `content`.
130
+ */
131
+ _collapsible: z.boolean().optional(),
132
+ /**
133
+ * TAL-186: an assistant row's text with each media reference the server serves rewritten to standard Markdown: an
134
+ * image to `![alt](url)`, any other file to `[name](url)`. Clients render it in place of `content` as one Markdown
135
+ * document; `content` stays as stored for copy and edit. Absent when the text has no such reference.
136
+ */
137
+ _display_content: z.string().optional(),
138
+ /** TAL-186: the media `_display_content` references, in content order. */
139
+ _media: z.array(DisplayMediaSchema).optional(),
95
140
  /**
96
141
  * TAL-371: an automatic background wakeup (delegation results, background process or watch notice), not a message the
97
142
  * user sent. Clients render it as a "Background update" disclosure: a localized label per `kind` (with `count`), a
@@ -106,6 +151,13 @@ export const MessageSchema = z.looseObject({
106
151
  */
107
152
  lines: z.array(BackgroundLineSchema),
108
153
  }).optional(),
154
+ /**
155
+ * TAL-305: a marker the Agent wrote around context compaction, not a message anyone typed. Clients render a collapsed
156
+ * card with a localized title per kind and the body on expansion: `_marker_body` when sent, else `content`.
157
+ */
158
+ _marker_kind: z.enum(['context_compaction', 'preserved_task_list']).optional(),
159
+ /** TAL-305: a preserved task list's card body, without its marker line. */
160
+ _marker_body: z.string().optional(),
109
161
  /** TAL-460: part of the Agent's reply to the background update just before it in the same turn. */
110
162
  _background_reply: z.boolean().optional(),
111
163
  /** TAL-460: that reply is only a silence marker; clients show the update's lines and nothing of this turn's reply. */
@@ -138,23 +190,41 @@ const ReadOnlySchema = z.boolean().describe('The session cannot be modified from
138
190
  const CanBranchSchema = z.boolean().describe('Web may branch this session: never a subagent child, and a read-only session only when it is a cron run.');
139
191
  const CanPinSchema = z.boolean().describe('Web may pin or unpin this session: never a subagent child.');
140
192
  const CanArchiveSchema = z.boolean().describe('Web may archive or unarchive this session: never a subagent child.');
193
+ const CanDeleteSchema = z.boolean().describe('Web may delete this session: never a read-only session (a read-only import, a view-only subagent child, or a foreign session whose owner refuses claiming).');
141
194
  const CanDuplicateSchema = z.boolean().describe('Web may duplicate this session: it has a WebUI copy and is not a subagent child.');
142
195
  // TAL-310: the server classifies every session's source once; clients file rows by it and never scan source markers.
143
196
  export const SourceKindSchema = z.enum(['webui', 'cli', 'messaging', 'cron', 'webhook', 'subagent', 'claude_code', 'kanban', 'api', 'other'])
144
197
  .describe('The session\'s source family. `is_cli_session` is true only for `cli` and `claude_code`.');
198
+ // TAL-306: clients keep the server's list order and date-bucket by this; they never sort or pick a timestamp themselves.
199
+ const SortTsSchema = z.number().describe('Epoch seconds the session sorts and date-buckets by: `last_message_at`, else `updated_at`, else `created_at`.');
145
200
  const IsMessagingSessionSchema = z.boolean().describe('`source_kind` is `messaging`: a gateway chat (Telegram, Signal, WhatsApp, …) the server imports before Web continues it.');
201
+ /**
202
+ * The label every client shows for the session's workspace: its registered name in the session profile's registry, else
203
+ * the folder name (TAL-303). Null without a workspace; absent from an older server, where clients show no label.
204
+ */
205
+ const WorkspaceNameSchema = NullableString.optional();
206
+ /**
207
+ * TAL-560: the "Context compaction · Reference only" card, on payloads that carry `messages`. Clients render `text` after
208
+ * the row at full-transcript index `after_message_index` (row `after_message_index - _messages_offset` of `messages`), or
209
+ * above the loaded rows when it is null or before them. Null: no card.
210
+ */
211
+ export const CompressionReferenceSchema = z.object({
212
+ text: z.string(),
213
+ after_message_index: z.number().int().nonnegative().nullable().describe('The full-transcript index of the row the card follows; null puts it above the transcript.'),
214
+ });
146
215
  /** Full session record from `GET /api/session` and mutations returning `session`. */
147
216
  export const SessionSchema = z.looseObject({
148
217
  session_id: SessionIdSchema, title: z.string(), workspace: z.string().optional(), created_workspace: z.string().nullable().optional(), model: NullableString.optional(), model_provider: NullableString.optional(),
218
+ model_option_id: NullableString.optional().describe('TAL-301: the catalog entry id the stored model/provider selects; null when none.'),
149
219
  messages: z.array(MessageSchema).optional(), tool_calls: z.array(ToolCallSchema).optional(), created_at: UnixSeconds.optional(), updated_at: UnixSeconds.optional(), last_message_at: NullableNumber.optional(),
150
220
  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(),
151
221
  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(),
152
222
  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(),
153
223
  pending_user_message: NullableString.optional(), pending_attachments: z.array(AttachmentSchema).optional(), pending_started_at: NullableNumber.optional(), pending_user_source: NullableString.optional(),
154
224
  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(),
225
+ 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, sort_ts: SortTsSchema, read_only: ReadOnlySchema, can_branch: CanBranchSchema, can_pin: CanPinSchema, can_archive: CanArchiveSchema, can_delete: CanDeleteSchema, can_duplicate: CanDuplicateSchema, source_tag: NullableString.optional(),
156
226
  source_label: NullableString.optional(), session_source: NullableString.optional(), raw_source: NullableString.optional(), parent_session_id: NullableString.optional(), worktree_path: NullableString.optional(),
157
- worktree_branch: NullableString.optional(), worktree_repo_root: NullableString.optional(), share_token: NullableString.optional(), share_created_at: NullableNumber.optional(), manual_title: z.boolean().optional(),
227
+ worktree_branch: NullableString.optional(), worktree_repo_root: NullableString.optional(), share_token: NullableString.optional(), share_url: NullableString.optional(), share_created_at: NullableNumber.optional(), manual_title: z.boolean().optional(),
158
228
  compression_anchor_summary: NullableString.optional(), compression_recovery: z.record(z.string(), Json).optional(), recommended_recovery_action: NullableString.optional(), compression_recovery_action: NullableString.optional(),
159
229
  compression_recovery_source_session_id: NullableString.optional(), gateway_routing: Json.optional(), _messages_offset: z.number().optional(), _messages_truncated: z.boolean().optional(), _msg_limit_max: z.number().optional(), _load_revision: z.string().optional(),
160
230
  /**
@@ -165,19 +235,22 @@ export const SessionSchema = z.looseObject({
165
235
  transcript_seq: z.object({ stream_id: z.string(), seq: z.number().int().nonnegative() }).nullable().optional(),
166
236
  /** The agent's display name: a named profile's own name, else the `bot_name` setting (TAL-458). */
167
237
  assistant_name: z.string().optional(),
238
+ workspace_name: WorkspaceNameSchema,
239
+ compression_reference: CompressionReferenceSchema.nullable().optional(),
168
240
  });
169
241
  export const SessionEnvelopeSchema = z.looseObject({ session: SessionSchema });
170
242
  /** Sidebar row from `GET /api/sessions`. */
171
243
  export const SessionRowSchema = z.looseObject({
172
244
  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(),
173
245
  message_count: z.number().optional(), pinned: z.boolean().optional(), archived: z.boolean().optional(), project_id: NullableString.optional(), profile: NullableString.optional(), is_streaming: IsStreamingSchema,
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(),
246
+ is_cli_session: z.boolean().optional(), source_kind: SourceKindSchema, is_messaging_session: IsMessagingSessionSchema, sort_ts: SortTsSchema, cron_running: z.boolean().optional(), read_only: ReadOnlySchema, can_branch: CanBranchSchema, can_pin: CanPinSchema, can_archive: CanArchiveSchema, can_delete: CanDeleteSchema, can_duplicate: CanDuplicateSchema, attention: z.looseObject({ kind: z.string().optional(), count: z.number().optional() }).nullable().optional(),
175
247
  source_tag: NullableString.optional(), source_label: NullableString.optional(), session_source: NullableString.optional(), raw_source: NullableString.optional(), parent_session_id: NullableString.optional(),
176
- active_stream_id: ActiveStreamIdSchema, share_token: NullableString.optional(), worktree_branch: NullableString.optional(), match_type: z.string().optional(), match_preview: NullableString.optional(),
248
+ active_stream_id: ActiveStreamIdSchema, share_token: NullableString.optional(), share_url: NullableString.optional(), worktree_branch: NullableString.optional(), match_type: z.enum(['title', 'metadata', 'content']).optional(), match_preview: NullableString.optional(),
249
+ workspace_name: WorkspaceNameSchema,
177
250
  ...ContextUsageFields,
178
251
  });
179
252
  export const SessionsListSchema = z.looseObject({
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(),
253
+ sessions: z.array(SessionRowSchema).describe('Canonical display order (TAL-306): pinned first, then active (streaming or a pending prompt), then newest `sort_ts`, then `session_id`. Clients keep it.'), 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(),
181
254
  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(),
182
255
  cli_session_count: z.number().int(),
183
256
  // TAL-482: sidebar counts for the "Scheduled sessions" / "Webhook sessions" groups; `_truncated` means more exist than are listed.
@@ -203,7 +276,12 @@ export const ChatStartResponseSchema = z.looseObject({
203
276
  stream_id: z.string().optional(), session_id: SessionIdSchema.optional(), turn_id: z.string().optional(), user_message_id: z.union([z.string(), z.number()]).nullable().optional(), pending_started_at: z.number().nullable().optional(),
204
277
  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(),
205
278
  });
206
- export const StreamStatusSchema = z.looseObject({ active: z.boolean(), stream_id: z.string(), replay_available: z.boolean().optional(), journal: Json.optional() });
279
+ /**
280
+ * `active` is whether this stream's channel is still live. `blocking_stream_id` (TAL-622) is the run that still holds the
281
+ * stream's session: this run while it unwinds after a Stop, or a successor such as a goal continuation. A session delete
282
+ * answers 409 while it is set, so a client draining a session before deleting it waits until it is null.
283
+ */
284
+ export const StreamStatusSchema = z.looseObject({ active: z.boolean(), stream_id: z.string(), replay_available: z.boolean().optional(), journal: Json.optional(), blocking_stream_id: z.string().nullable() });
207
285
  /**
208
286
  * TAL-424: a pending steer is no longer pending without the Agent taking it: Edit and Cancel by the user, `stopped` by a
209
287
  * Stop (`text` goes back to the composer), or `followup` when the server sends it as the next turn. `steer_id` is null
@@ -250,7 +328,9 @@ export const ClarifyRespondRequestSchema = z.looseObject({ session_id: SessionId
250
328
  export const ClarifyRespondResponseSchema = z.looseObject({ ok: z.boolean(), response: z.string().optional(), error: z.string().optional(), stale: z.boolean().optional() });
251
329
  export const DraftSchema = z.looseObject({ text: z.string(), files: z.array(Json) });
252
330
  export const DraftResponseSchema = z.looseObject({ ok: z.literal(true), draft: DraftSchema, draft_version: z.string().nullable(), unchanged: z.boolean().optional() });
253
- export const UploadResponseSchema = z.looseObject({ filename: z.string(), path: z.string(), size: z.number(), mime: z.string(), is_image: z.boolean().optional(), rollback_token: z.string().optional() });
331
+ /** At most this many attachments ride one chat message; the server names each in the prompt (TAL-276, TAL-635). */
332
+ export const MAX_CHAT_ATTACHMENTS = 20;
333
+ export const UploadResponseSchema = z.looseObject({ filename: z.string(), path: z.string(), size: z.number(), mime: z.string(), is_image: z.boolean().optional(), rollback_token: z.string().optional(), named_in_prompt: z.boolean().optional(), max_attachments_per_message: z.number().int().positive().optional() });
254
334
  export const GoalViewSchema = z.looseObject({ text: z.string().optional(), state: z.string().optional(), status: z.string().optional(), turns: z.number().optional(), max_turns: z.number().optional(), reason: z.string().optional() });
255
335
  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() });
256
336
  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() });
@@ -301,6 +381,11 @@ export const SessionNewRequestSchema = z.looseObject({
301
381
  });
302
382
  export const TranscribeCapabilitySchema = z.looseObject({ ok: z.literal(true), available: z.boolean(), provider: z.string() });
303
383
  // ── settings, profiles, models, providers ───────────────────────────────
384
+ /** TAL-411: the request profile's quota urgency thresholds. A save patches valid fields and clamps critical to at most warning. */
385
+ export const QuotaThresholdsSchema = z.object({
386
+ warning_remaining_percent: z.number().int().min(1).max(99), critical_remaining_percent: z.number().int().min(0).max(99), pace_tolerance_percent: z.number().int().min(0).max(25),
387
+ pace_warning_burn_rate_percent: z.number().int().min(100).max(300), pace_critical_burn_rate_percent: z.number().int().min(100).max(400), pace_minimum_elapsed_hours: z.number().int().min(0).max(72),
388
+ });
304
389
  export const LoginResponseSchema = z.looseObject({ ok: z.literal(true), message: z.string().optional() });
305
390
  export const SettingsSchema = z.looseObject({
306
391
  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(),
@@ -311,6 +396,9 @@ export const SettingsSchema = z.looseObject({
311
396
  auth_disabled_acknowledged: z.boolean().optional(), webui_version: z.string().optional(), agent_version: z.string().optional(), update_channel: z.string().optional(), agent_update_channel: z.enum(['stable', 'experimental']).optional(), update_channel_version: NullableString.optional(),
312
397
  max_tokens: NullableNumber.optional(), max_tokens_effective: NullableNumber.optional(), max_tokens_fallback: NullableNumber.optional(), tts_engine: z.string().optional(), tts_voice: z.string().optional(),
313
398
  dictation_append: z.boolean().optional(), persisted_speech_keys: z.array(z.string()).optional(),
399
+ /** TAL-279: ask before opening external chat links; `trusted_link_hosts` are exact, server-normalized hostnames that skip the ask. */
400
+ confirm_external_links: z.boolean().optional(), trusted_link_hosts: z.array(z.string()).optional(),
401
+ provider_quota_thresholds: QuotaThresholdsSchema.optional(),
314
402
  });
315
403
  export const ProfileSchema = z.looseObject({
316
404
  name: z.string(), path: z.string().optional(), is_active: z.boolean().optional(), is_default: z.boolean().optional(), model: NullableString.optional(), provider: NullableString.optional(), skill_count: z.number().optional(),
@@ -321,20 +409,82 @@ export const ActiveProfileSchema = z.looseObject({ name: z.string(), path: z.str
321
409
  export const ProfileSwitchSchema = ProfilesSchema.extend({ is_default: z.boolean(), default_model: Json, default_model_provider: Json, default_workspace: z.string().nullable() });
322
410
  /** `/api/reasoning`: config.yaml agent.reasoning_effort / display.show_reasoning, resolved for a model. */
323
411
  export const ReasoningStatusSchema = z.looseObject({ show_reasoning: z.boolean().optional(), reasoning_effort: z.string().nullable().optional(), supported_efforts: z.array(z.string()).optional(), supports_reasoning_effort: z.boolean().optional(), supports_thinking_toggle: z.boolean().optional() });
324
- export const ModelEntrySchema = z.looseObject({ id: z.string(), label: z.string().optional(), provider: z.string().optional(), supports_fast_tier: z.boolean().optional() });
412
+ /** TAL-301: `provider_id`/`bare_id` are the server's split of `id`; clients match a stored `(model, provider)` against them and send `id` back. */
413
+ export const ModelEntrySchema = z.looseObject({ id: z.string(), label: z.string().optional(), provider: z.string().optional(), provider_id: z.string().optional(), bare_id: z.string().optional(), supports_fast_tier: z.boolean().optional() });
325
414
  export const ModelGroupSchema = z.looseObject({ provider: z.string(), provider_id: z.string().optional(), models: z.array(ModelEntrySchema), extra_models: z.array(ModelEntrySchema).optional() });
326
- export const ModelsSchema = z.looseObject({ active_provider: NullableString.optional(), default_model: z.string().optional(), groups: z.array(ModelGroupSchema), aliases: z.record(z.string(), Json).optional(), configured_model_badges: z.record(z.string(), Json).optional() });
415
+ export const ModelsSchema = z.looseObject({ active_provider: NullableString.optional(), default_model: z.string().optional(), default_provider_id: NullableString.optional(), default_bare_id: z.string().optional(), default_option_id: NullableString.optional(), groups: z.array(ModelGroupSchema), aliases: z.record(z.string(), Json).optional(), configured_model_badges: z.record(z.string(), Json).optional() });
416
+ /** `has_key` reports a credential; `configured` (TAL-570) is the row's setup status, which a keyless Ollama or LM Studio endpoint also meets. */
327
417
  export const ProviderSchema = z.looseObject({
328
- id: z.string(), display_name: z.string().optional(), has_key: z.boolean().optional(), configurable: z.boolean().optional(), is_oauth: z.boolean().optional(), is_plugin_provider: z.boolean().optional(), is_self_hosted: z.boolean().optional(),
418
+ id: z.string(), display_name: z.string().optional(), has_key: z.boolean().optional(), configured: z.boolean().optional(), configurable: z.boolean().optional(), is_oauth: z.boolean().optional(), is_plugin_provider: z.boolean().optional(), is_self_hosted: z.boolean().optional(),
329
419
  is_custom: z.boolean().optional(), key_source: z.string().optional(), base_url: NullableString.optional(), auth_error: NullableString.optional(), env_var: NullableString.optional(), models: z.array(ModelEntrySchema).optional(), models_total: z.number().optional(),
330
420
  });
331
421
  export const ProvidersSchema = z.looseObject({ providers: z.array(ProviderSchema), active_provider: NullableString.optional() });
422
+ export const QuotaLevelSchema = z.enum(['healthy', 'warning', 'critical', 'stale', 'unavailable']);
423
+ /** TAL-411: the server's classification per colour basis; `/api/provider/quotas` only, at the profile's thresholds. */
424
+ export const QuotaUrgencySchema = z.looseObject({ remaining: QuotaLevelSchema, pace: QuotaLevelSchema.describe('Burn rate and pace tolerance; the remaining classification when the window has no pace.') });
425
+ /** TAL-409: a window's pace as of the envelope's `computed_at`. Stale once `valid_until` (the window's reset) passes. */
426
+ export const QuotaPaceSchema = z.looseObject({
427
+ expected_remaining_percent: z.number(), pace_delta_percent: z.number(), burn_rate: z.number(), minutes_to_reset: z.number(),
428
+ projected_minutes_to_empty: NullableNumber.describe('Minutes until the window empties at the current burn; null when nothing has been used.'),
429
+ elapsed_minutes: z.number(), valid_until: z.string(),
430
+ status: z.enum(['over', 'on', 'under']).optional().describe('TAL-411, `/api/provider/quotas` only: over when behind pace by at least the tolerance, under when more than 1 point ahead.'),
431
+ });
432
+ export const QuotaForecastSchema = z.looseObject({
433
+ outcome: z.enum(['safe', 'warning']).describe('`warning` when the projection empties the window before it resets.'),
434
+ budget_unit: z.enum(['hour', 'day']), budget_percent: NullableNumber.describe('Remaining percent per `budget_unit` until reset.'),
435
+ depletion_margin_minutes: NullableNumber.describe('Projected empty minus reset, in minutes; negative empties early, null when no depletion is projected.'),
436
+ });
437
+ export const QuotaWindowSchema = z.looseObject({
438
+ label: z.string(), used_percent: NullableNumber, remaining_percent: NullableNumber, reset_at: NullableString.describe('ISO-8601 UTC.'), detail: NullableString,
439
+ window_seconds: NullableNumber.describe('The provider value, else 5h or weekly from the label; null when unknown.'),
440
+ pace: QuotaPaceSchema.nullable().describe('Null for a window without a future reset, a usage value, or a 5h/weekly length.'),
441
+ forecast: QuotaForecastSchema.nullable(),
442
+ projection_eligible: z.boolean().optional().describe('TAL-411, `/api/provider/quotas` only: the burn-rate breakpoints apply (enough elapsed time and use, and the projection empties the window before reset).'),
443
+ urgency: QuotaUrgencySchema.optional(),
444
+ });
445
+ const QuotaWindowIndex = z.number().int().nullable().optional();
332
446
  export const QuotaSourceSchema = z.looseObject({
333
447
  source_id: z.string(), provider_id: z.string().optional(), provider_label: z.string().optional(), account_label: z.string().optional(), status: z.string().optional(), supported: z.boolean().optional(), message: z.string().nullable().optional(),
334
- is_active_provider: z.boolean().optional(), quota: Json.optional(), windows: Json.optional(), balances: Json.optional(), plan: Json.optional(), details: Json.optional(), unavailable_reason: Json.optional(), retry_after: Json.optional(), fetched_at: Json.optional(),
448
+ is_active_provider: z.boolean().optional(), quota: Json.optional(), windows: z.array(QuotaWindowSchema).optional(), balances: Json.optional(), plan: Json.optional(), details: Json.optional(), unavailable_reason: Json.optional(), retry_after: Json.optional(), fetched_at: Json.optional(),
449
+ pace_window_index: QuotaWindowIndex.describe('The window a pace-coloured widget shows: the weekly one, else the first 5h/session window.'),
450
+ session_window_index: QuotaWindowIndex.describe('The session (else 5h) window.'), weekly_window_index: QuotaWindowIndex.describe('The weekly window.'),
451
+ urgency: QuotaUrgencySchema.optional().describe('TAL-411: `remaining` of the first window, `pace` of the pace window; alerts and the source colour follow it.'),
452
+ });
453
+ /** An account-usage provider's normalised snapshot on `/api/provider/quota` (the same windows and indexes as a quotas source). */
454
+ export const QuotaAccountLimitsSchema = z.looseObject({
455
+ available: z.boolean().optional(), stale: z.boolean().optional(), title: z.string().optional(), plan: Json.optional(), unavailable_reason: NullableString.optional(),
456
+ windows: z.array(QuotaWindowSchema), details: Json.optional(), fetched_at: NullableString.describe('ISO-8601 UTC.'),
457
+ pace_window_index: QuotaWindowIndex, session_window_index: QuotaWindowIndex, weekly_window_index: QuotaWindowIndex,
458
+ });
459
+ /** Python `get_provider_quota`: one provider's quota status. */
460
+ export const ProviderQuotaSchema = z.looseObject({
461
+ computed_at: z.string().describe('ISO-8601 UTC reference time of every window `pace`.'),
462
+ ok: z.boolean(), provider: NullableString, display_name: NullableString, supported: z.boolean(), status: z.string(), label: z.string().optional(), message: z.string(),
463
+ quota: Json.describe('OpenRouter credits (`limit_remaining`, `usage`, `limit`), else null.'),
464
+ account_limits: QuotaAccountLimitsSchema.nullable().optional(),
335
465
  });
336
466
  /** Python `get_provider_quotas`: the stable identity envelope the iOS quota widget persists (`scope_id`/`profile_id`). */
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) });
467
+ export const ProviderQuotasSchema = z.looseObject({
468
+ version: z.number(), computed_at: z.string().describe('ISO-8601 UTC reference time of every window `pace`.'), scope_id: z.string(), profile_id: z.string(), active_provider: NullableString, requested_source_id: NullableString,
469
+ missing_source: z.boolean().describe('True when `?source=` names a source id this scope no longer has; `sources` is then empty.'),
470
+ sources: z.array(QuotaSourceSchema).describe('Each live source id exactly once, ordered by `provider_id`, then `account_label`, then `source_id`. Clients render this list as-is; a `?source=` read returns that one row.'),
471
+ });
472
+ /** One day of OpenRouter spend on `/api/provider/cost-history` (TAL-412). */
473
+ export const CostSnapshotSchema = z.looseObject({
474
+ date: z.string().describe('UTC day, `YYYY-MM-DD`.'), used: NullableNumber.describe('Cumulative OpenRouter usage in USD that day.'),
475
+ delta: NullableNumber.describe("Spend since the previous day; the day's `used` after a credit reset; null for the first day or a missing `used`."),
476
+ bar_percent: z.number().describe('Bar height: `delta` as a percent of the window\'s largest delta, at least 2 for any positive delta, 0 for a null delta.'),
477
+ });
478
+ /** Python `get_provider_cost_history`: OpenRouter daily spend with the server-computed monthly pace and budget standing. */
479
+ export const ProviderCostHistorySchema = z.looseObject({
480
+ ok: z.boolean(), provider: NullableString, display_name: z.string().optional(), supported: z.boolean().optional(), status: z.string().describe('`available`, `unavailable` (the snapshots are the last known data), `no_key`, `unsupported`, or `missing_provider`.'), message: z.string(),
481
+ window_days: z.number().int().optional(), snapshots: z.array(CostSnapshotSchema).optional(), limit: NullableNumber.optional(), label: NullableString.optional(),
482
+ monthly_budget: NullableNumber.optional().describe('Settings `provider_cost_budget`.'),
483
+ monthly_pace: NullableNumber.optional().describe('Mean of the non-null deltas × 30, in USD rounded to cents; null without a delta.'),
484
+ has_enough_data: z.boolean().optional().describe('At least one non-null delta (two daily snapshots).'),
485
+ budget_percent: NullableNumber.optional().describe('round(pace / budget × 100); null without a positive pace and a budget.'),
486
+ budget_level: z.enum(['ok', 'warn', 'over']).nullable().optional().describe('`warn` from 80%, `over` from 100%; null without `budget_percent`.'),
487
+ });
338
488
  export const PersonalitiesSchema = z.looseObject({ personalities: z.array(z.looseObject({ name: z.string(), description: z.string().optional() })) });
339
489
  /** One auxiliary task slot in server order (TAL-388). The server owns ordering, normalization, and catalog matching. */
340
490
  export const AuxiliaryTaskSchema = z.looseObject({
@@ -354,14 +504,23 @@ export const AuxiliaryTaskSchema = z.looseObject({
354
504
  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) });
355
505
  export const MaxTokensSchema = z.looseObject({ max_tokens: NullableNumber, max_tokens_effective: NullableNumber, max_tokens_fallback: NullableNumber });
356
506
  // ── workspaces, files, git ───────────────────────────────────────────────
357
- export const WorkspaceEntrySchema = z.looseObject({ name: z.string().optional(), path: z.string() });
507
+ /** A registry entry; `name` is never empty (the folder name when none is registered, `Home` for `default`), so pickers show it as-is (TAL-303). */
508
+ export const WorkspaceEntrySchema = z.looseObject({ name: z.string().min(1), path: z.string() });
358
509
  export const WorkspacesSchema = z.looseObject({ workspaces: z.array(WorkspaceEntrySchema), last: NullableString.optional(), terminal_remote_backend: z.boolean().optional() });
359
510
  export const FileEntrySchema = z.looseObject({
360
511
  name: z.string(), path: z.string().optional(), is_dir: z.boolean().optional(), type: z.string().optional(), size: z.number().nullable().optional(), mtime: z.number().nullable().optional(), mtime_ns: z.union([z.number(), z.string()]).nullable().optional(),
361
512
  birthtime_ns: z.union([z.number(), z.string()]).nullable().optional(), hidden: z.boolean().optional(), workspace_sort_rank: z.number().int().optional(), target: z.string().optional(), target_outside_workspace: z.boolean().optional(),
362
513
  });
363
514
  export const DirListingSchema = z.looseObject({ path: z.string().optional(), entries: z.array(FileEntrySchema), signature: z.string().optional(), workspace: z.string().optional(), workspace_recovered: z.boolean().optional(), is_git: z.boolean().optional() });
364
- export const FileContentSchema = z.looseObject({ path: z.string().optional(), content: z.string().optional(), lines: z.number().optional(), size: z.number().optional(), truncated: z.boolean().optional(), binary: z.boolean().optional(), mime: z.string().optional() });
515
+ /** How a workspace file renders (TAL-566): media streams from `/api/file/raw`; text kinds carry `content`. */
516
+ export const FilePreviewKindSchema = z.enum(['text', 'markdown', 'csv', 'html', 'image', 'pdf', 'audio', 'video', 'binary']);
517
+ /** `table` is a `csv` preview's leading rows (header first); `table_truncated` says the file has more. `preview_url` is an
518
+ * `html` preview's app-relative frame URL, under which the page's relative assets also resolve. */
519
+ export const FileContentSchema = z.looseObject({
520
+ path: z.string().optional(), content: z.string().optional(), lines: z.number().optional(), size: z.number().optional(), truncated: z.boolean().optional(), binary: z.boolean().optional(), mime: z.string().optional(),
521
+ preview: FilePreviewKindSchema.optional(), table: z.array(z.array(z.string())).optional(), table_truncated: z.boolean().optional(),
522
+ preview_url: z.string().optional(),
523
+ });
365
524
  export const GitInfoSchema = z.looseObject({ git: z.looseObject({ is_git: z.boolean().optional(), branch: NullableString.optional(), dirty: z.number().optional(), modified: z.number().optional(), untracked: z.number().optional(), ahead: z.number().optional(), behind: z.number().optional() }).nullable().optional() });
366
525
  // ── tools ────────────────────────────────────────────────────────────────
367
526
  export const SkillSchema = z.looseObject({ name: z.string(), description: NullableString.optional(), category: NullableString.optional(), disabled: z.boolean().optional() });
@@ -375,7 +534,20 @@ export const MemorySchema = z.looseObject({
375
534
  });
376
535
  export const PromptSchema = z.looseObject({ id: z.string().optional(), name: z.string().optional(), title: z.string().optional(), label: z.string().optional(), text: z.string().optional(), content: z.string().optional(), created_at: z.number().optional() });
377
536
  export const PromptsSchema = z.looseObject({ prompts: z.array(PromptSchema) });
378
- export const CommandRowSchema = z.looseObject({ name: z.string(), description: z.string().optional(), aliases: z.array(z.string()).optional(), args_hint: z.string().optional(), category: z.string().optional(), cli_only: z.boolean().optional(), gateway_only: z.boolean().optional(), subcommands: z.array(Json).optional() });
537
+ /**
538
+ * One slash command in the server's canonical catalog (TAL-314). `GET /api/commands` lists the client-handled commands
539
+ * first, then the Agent registry, in display order; each name appears once. `handler` says who runs it, `clients` which
540
+ * clients can run it, and `unsupported_message` is the English text a client outside `clients` shows when it is typed.
541
+ * Clients suggest the entries listing them whose name or any alias starts with the typed text (case-insensitive), and
542
+ * resolve a typed alias to the entry's `name`. `exec` marks an Agent entry `POST /api/commands/exec` runs: the client
543
+ * posts the typed text there and shows the returned output instead of starting a chat turn (TAL-561).
544
+ */
545
+ export const CommandClientSchema = z.enum(['web', 'ios']);
546
+ export const CommandRowSchema = z.looseObject({
547
+ name: z.string(), description: z.string().optional(), aliases: z.array(z.string()), args_hint: z.string().optional(), category: z.string().optional(),
548
+ handler: z.enum(['client', 'agent']), clients: z.array(CommandClientSchema), unsupported_message: z.string().optional(),
549
+ cli_only: z.boolean().optional(), gateway_only: z.boolean().optional(), subcommands: z.array(Json).optional(), exec: z.boolean().optional(),
550
+ });
379
551
  export const CommandsSchema = z.looseObject({ commands: z.array(CommandRowSchema) });
380
552
  export const LogsSchema = z.looseObject({ file: z.string(), tail: z.number().optional(), lines: z.array(z.string()), truncated: z.boolean().optional(), total_bytes: z.number().optional(), mtime: NullableNumber.optional(), hint: z.string().optional() });
381
553
  export const InsightsSchema = z.looseObject({
@@ -390,10 +562,17 @@ export const SystemHealthSchema = z.looseObject({
390
562
  available: z.boolean().optional(), status: z.string().optional(), checked_at: z.string().optional(), cpu: Json.nullable().optional(), memory: Json.nullable().optional(), disk: z.looseObject({ percent: z.number().optional(), total_bytes: z.number().optional(), used_bytes: z.number().optional() }).nullable().optional(),
391
563
  errors: z.array(z.looseObject({ code: z.string().optional(), metric: z.string().optional() })).optional(), webui_runtime: Json.optional(),
392
564
  });
393
- export const UpdateTargetSchema = z.looseObject({ name: z.string().optional(), channel: z.enum(['stable', 'experimental']).optional(), unsupported: z.boolean().optional(), supported_revision: z.string().optional(), supported_version: z.string().optional(), candidate_revision: z.string().optional(), behind: z.number().nullable().optional(), current_sha: z.string().nullable().optional(), latest_sha: z.string().nullable().optional(), compare_url: z.string().optional(), repo_url: z.string().optional(), error: z.string().optional(), ok: z.boolean().optional(), manual_update: z.boolean().optional(), no_git: z.boolean().optional(), install_kind: z.literal('npm').optional(), npm: z.string().optional(), channel_switch: z.boolean().optional(), dirty: z.boolean().optional(), metadata_repair: z.boolean().optional(), release_url: z.string().optional(), ignored: z.boolean().optional(), stale_check: z.boolean().optional(), current_version: z.string().optional() });
565
+ export const UpdateStateSchema = z.enum(['up_to_date', 'release_ready', 'commits_behind', 'local_changes', 'finish', 'manual', 'check_failed', 'off', 'unknown']);
566
+ export const UpdateTargetSchema = z.looseObject({ name: z.string().optional(), channel: z.enum(['stable', 'experimental']).optional(), unsupported: z.boolean().optional(), supported_revision: z.string().optional(), supported_version: z.string().optional(), candidate_revision: z.string().optional(), behind: z.number().nullable().optional(), current_sha: z.string().nullable().optional(), latest_sha: z.string().nullable().optional(), compare_url: z.string().optional(), repo_url: z.string().optional(), error: z.string().optional(), ok: z.boolean().optional(), manual_update: z.boolean().optional(), no_git: z.boolean().optional(), install_kind: z.literal('npm').optional(), npm: z.string().optional(), channel_switch: z.boolean().optional(), dirty: z.boolean().optional(), metadata_repair: z.boolean().optional(), release_url: z.string().optional(), ignored: z.boolean().optional(), stale_check: z.boolean().optional(), current_version: z.string().optional(),
567
+ state: UpdateStateSchema.describe('TAL-559: the status clients word; `off` when checks are disabled or Agent updates are ignored.'),
568
+ can_apply: z.boolean().describe('The apply action is available for this target.'),
569
+ installed_unverified: z.boolean().describe('`release_ready` only: the installed revision sits on no release tag.'),
570
+ manual_link: z.boolean().describe('Show the manual-install (releases) link.'),
571
+ target_version: z.string().nullable().describe('The version the update path leads to, when one is shown.'),
572
+ });
394
573
  export const UpdatesCheckSchema = z.looseObject({ disabled: z.boolean().optional(), cached: z.boolean().optional(), channel: z.string().optional(), agent_channel: z.enum(['stable', 'experimental']).optional(), checked_at: z.number().optional(), include_agent: z.boolean().optional(), webui: UpdateTargetSchema.nullable().optional(), agent: UpdateTargetSchema.nullable().optional() });
395
574
  export const UpdatesSummarySchema = z.looseObject({ summary: z.string().optional(), text: z.string().optional(), ok: z.boolean().optional(), error: z.string().optional(), diff_links: z.array(Json).optional() });
396
- export const UpdateApplySchema = z.looseObject({ ok: z.boolean().optional(), status: z.string().optional(), error: z.string().optional(), message: z.string().optional(), lock: Json.optional(), confirmation_required: z.boolean().optional(), candidate_revision: z.string().optional(), supported_revision: z.string().optional(), supported_version: z.string().optional(), agent_channel: z.enum(['stable', 'experimental']).optional(), notification_id: z.uuid().optional(), channel_switch: z.boolean().optional(), backup_dir: z.string().nullable().optional(), backup_failed: z.boolean().optional() });
575
+ export const UpdateApplySchema = z.looseObject({ ok: z.boolean().optional(), status: z.string().optional(), error: z.string().optional(), message: z.string().optional(), lock: Json.optional(), confirmation_required: z.boolean().optional(), candidate_revision: z.string().optional(), supported_revision: z.string().optional(), supported_version: z.string().optional(), agent_channel: z.enum(['stable', 'experimental']).optional(), notification_id: z.uuid().optional(), channel_switch: z.boolean().optional(), backup_dir: z.string().nullable().optional(), backup_failed: z.boolean().optional(), stash_conflict: z.boolean().optional() });
397
576
  export const UpdateNotificationPhaseSchema = z.enum(['applying', 'awaiting_confirmation', 'restarting', 'succeeded', 'blocked', 'failed', 'unknown']);
398
577
  export const UpdateNotificationActionSchema = z.object({
399
578
  id: z.string().min(1).max(64).regex(/^[a-z0-9][a-z0-9_-]*$/),
@@ -424,7 +603,7 @@ export const UpdateNotificationSchema = z.object({
424
603
  acknowledged_action_id: z.string().nullable(),
425
604
  verified_revision: z.string().regex(/^[a-f0-9]{40}$/).nullable(),
426
605
  verified_version: z.string().min(1).max(80).nullable(),
427
- /** The apply's own explanation of a failed or blocked attempt, line breaks kept; null in every other phase. */
606
+ /** The apply's own explanation of a failed or blocked attempt, or of a success that needs attention (an Agent update that kept local edits in the git stash), line breaks kept; null otherwise. */
428
607
  detail: z.string().max(2000).nullable(),
429
608
  unread: z.boolean(),
430
609
  active: z.boolean(),
@@ -454,14 +633,15 @@ export const UpdateNotificationsSchema = z.object({
454
633
  });
455
634
  export const PluginSchema = z.looseObject({ key: z.string(), name: z.string().optional(), kind: z.string().optional(), enabled: z.boolean().optional(), description: z.string().optional(), version: z.string().optional(), activation: z.string().optional(), is_active_provider: z.boolean().optional(), hooks: z.array(Json).optional() });
456
635
  export const PluginsSchema = z.looseObject({ plugins: z.array(PluginSchema), empty: z.boolean().optional(), read_only: z.boolean().optional(), supported_hooks: z.array(z.string()).optional(), unavailable: z.boolean().optional() });
457
- export const McpServerSchema = z.looseObject({ name: z.string(), id: z.string().optional(), transport: z.string().optional(), enabled: z.boolean().optional(), active: z.boolean().optional(), status: z.string().optional(), tools: z.number().optional(), tool_count: z.number().int().nullable().optional(), health: z.string().optional(), health_detail: z.string().optional(), health_checked_at: Json.optional(), health_pending: z.boolean().optional() });
636
+ export const McpServerSchema = z.looseObject({ name: z.string(), id: z.string().optional(), transport: z.string().optional(), enabled: z.boolean().optional(), can_toggle: z.boolean().optional().describe('Whether PATCH can enable or disable this server; false for invalid config.'), active: z.boolean().optional(), status: z.string().optional(), tools: z.number().optional(), tool_count: z.number().int().nullable().optional(), health: z.string().optional(), health_detail: z.string().optional(), health_checked_at: Json.optional(), health_pending: z.boolean().optional() });
458
637
  export const McpServersSchema = z.looseObject({ servers: z.array(McpServerSchema), health_pending: z.boolean().optional(), reload_required: z.boolean().optional(), toggle_supported: z.boolean().optional() });
459
638
  export const McpToolsSchema = z.looseObject({ tools: z.array(z.looseObject({ name: z.string().optional(), server: z.string().optional(), description: z.string().optional() })), total: z.number().int().optional(), source: z.string().optional(), inventory_scope: z.string().optional(), unavailable_servers: z.array(z.string()).optional() });
460
639
  export const NotesSourcesSchema = z.looseObject({ enabled: z.boolean().optional(), sources: z.array(Json).optional(), source: z.string().optional(), inventory_scope: z.string().optional(), recent_ai_notes: z.array(Json).optional(), attach_supported: z.boolean().optional(), automatic_recall_unchanged: z.boolean().optional() });
461
640
  export const TodoItemSchema = z.looseObject({ id: z.union([z.string(), z.number()]).optional(), text: z.string().optional(), content: z.string().optional(), title: z.string().optional(), status: z.string().optional(), done: z.boolean().optional(), completed: z.boolean().optional() });
462
- export const TodoStateSchema = z.looseObject({ session_id: z.string().optional(), todos: z.array(TodoItemSchema).optional(), version: z.number().optional(), ts: z.number().optional(), source: z.string().optional(), description: z.string().optional(), pending_count: z.number().optional() });
641
+ export const TodoStateSchema = z.looseObject({ session_id: z.string().optional(), todos: z.array(TodoItemSchema).optional(), summary: z.record(z.string(), z.unknown()).optional(), version: z.number().optional(), ts: z.number().optional(), source: z.string().optional(), description: z.string().optional(), pending_count: z.number().optional() });
463
642
  // ── onboarding ───────────────────────────────────────────────────────────
464
- export const OnboardingProviderSchema = z.looseObject({ id: z.string(), name: z.string().optional(), label: z.string().optional(), kind: z.string().optional(), oauth: z.boolean().optional(), needs_key: z.boolean().optional(), base_url: NullableString.optional(), models: z.array(Json).optional(), category: z.string().optional() });
643
+ /** `oauth_flow` (TAL-398): `device_code` when the provider signs in through `/api/onboarding/oauth/*` instead of taking an API key; `oauth_label` then names the account to sign in with, and `signed_in` says whether the profile already holds its credential. */
644
+ export const OnboardingProviderSchema = z.looseObject({ id: z.string(), name: z.string().optional(), label: z.string().optional(), kind: z.string().optional(), oauth: z.boolean().optional(), needs_key: z.boolean().optional(), base_url: NullableString.optional(), models: z.array(Json).optional(), category: z.string().optional(), default_model: z.string().optional(), oauth_flow: z.literal('device_code').nullable().optional(), oauth_label: z.string().optional(), signed_in: z.boolean().optional() });
465
645
  export const OnboardingStatusSchema = z.looseObject({
466
646
  completed: z.boolean(),
467
647
  settings: z.looseObject({ bot_name: z.string().optional(), default_model: z.string().optional(), default_workspace: NullableString.optional(), password_enabled: z.boolean().optional() }).optional(),
@@ -471,16 +651,25 @@ export const OnboardingStatusSchema = z.looseObject({
471
651
  models: ModelsSchema.optional(),
472
652
  });
473
653
  export const OnboardingProbeSchema = z.looseObject({ ok: z.boolean().optional(), success: z.boolean().optional(), error: z.string().optional(), message: z.string().optional(), models: z.array(Json).optional() });
474
- export const OnboardingOAuthSchema = z.looseObject({ ok: z.boolean().optional(), status: z.string().optional(), url: z.string().optional(), verification_url: z.string().optional(), user_code: z.string().optional(), message: z.string().optional(), error: z.string().optional(), flow_id: z.string().optional() });
654
+ /**
655
+ * One device-code sign-in (TAL-398). `status` is `pending` until the flow ends `approved`, `denied`, `expired`, `cancelled`
656
+ * or `error`; `ok` is false for every ending without a credential, and `error` then carries the Agent's reason.
657
+ * `user_code`, `verification_url`, `expires_in` and `interval` (seconds between polls) come with the start.
658
+ */
659
+ export const OnboardingOAuthSchema = z.looseObject({
660
+ ok: z.boolean().optional(), status: z.string().optional(), url: z.string().optional(), verification_url: z.string().optional(), user_code: z.string().optional(), message: z.string().optional(),
661
+ error: z.string().nullable().optional(), flow_id: z.string().optional(), provider: z.string().optional(), expires_in: z.number().int().optional(), interval: z.number().int().optional(),
662
+ });
475
663
  // ── automation ───────────────────────────────────────────────────────────
476
664
  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() });
477
665
  export const CronRepeatSchema = z.looseObject({ times: NullableNumber.optional(), completed: z.number().optional() });
478
666
  export const CronDerivedStateSchema = z.enum(['needs_attention', 'schedule_error', 'paused', 'off', 'error', 'active']);
479
667
  export const CronJobViewSchema = z.looseObject({
480
668
  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(),
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(),
669
+ /** Server-filled (TAL-298): `schedule_display` is the schedule text clients show; `schedule_input` is the editor prefill the scheduler accepts back. */
670
+ schedule_display: z.string(), schedule_input: z.string(), enabled: z.boolean().optional(), paused: z.boolean().optional(), paused_reason: NullableString.optional(), state: NullableString.optional(), last_status: NullableString.optional(), last_error: NullableString.optional(),
482
671
  last_delivery_error: NullableString.optional(), next_run_at: z.union([z.string(), z.number(), z.null()]).optional(), last_run_at: z.union([z.string(), z.number(), z.null()]).optional(), repeat: z.union([CronRepeatSchema, z.number(), z.null()]).optional(),
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(),
672
+ 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(), model_option_id: NullableString.optional(), workspace: NullableString.optional(),
484
673
  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(),
485
674
  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
675
  /** Server-derived (TAL-296): the job's status, whether it needs attention, and whether its primary action is Resume. Live runs overlay it. */
@@ -500,10 +689,26 @@ export const CronRunSummarySchema = z.looseObject({ filename: z.string(), size:
500
689
  export const CronHistorySchema = z.looseObject({ job_id: z.string().optional(), runs: z.array(CronRunSummarySchema), total: z.number().optional(), offset: z.number().optional() });
501
690
  export const CronRunSchema = z.looseObject({ content: z.string().optional(), snippet: z.string().optional(), usage: CronRunUsageSchema.optional(), error: z.string().optional() });
502
691
  export const CronStatusSchema = z.looseObject({ running: z.union([z.boolean(), z.record(z.string(), z.number())]).optional() });
503
- export const KanbanTaskViewSchema = z.looseObject({ id: z.union([z.string(), z.number()]), title: z.string().optional(), status: z.string().optional(), assignee: NullableString.optional(), priority: z.union([z.string(), z.number()]).nullable().optional(), description: z.string().optional(), tags: z.array(z.string()).optional(), session_id: NullableString.optional(), archived: z.boolean().optional(), created_at: Json.optional(), updated_at: Json.optional() });
692
+ /** Card actions the server offers for a task's current status (TAL-557); clients show exactly these. `move_to` lists the statuses a Move may target, in display order. */
693
+ export const KanbanTaskActionsSchema = z.object({ block: z.boolean(), unblock: z.boolean(), complete: z.boolean(), archive: z.boolean(), move_to: z.array(z.string()) });
694
+ /** Server-computed card policy on every task the Kanban routes return (TAL-557). `requires_running_exit_confirmation`: any action takes the task out of a live run, so clients confirm first. */
695
+ export const KanbanTaskPolicyShape = { available_actions: KanbanTaskActionsSchema, requires_running_exit_confirmation: z.boolean() };
696
+ export const KanbanTaskViewSchema = z.looseObject({ id: z.union([z.string(), z.number()]), title: z.string().optional(), status: z.string().optional(), assignee: NullableString.optional(), priority: z.union([z.string(), z.number()]).nullable().optional(), description: z.string().optional(), tags: z.array(z.string()).optional(), session_id: NullableString.optional(), archived: z.boolean().optional(), created_at: Json.optional(), updated_at: Json.optional(), ...KanbanTaskPolicyShape });
504
697
  export const KanbanColumnSchema = z.looseObject({ name: z.string(), tasks: z.array(KanbanTaskViewSchema) });
505
- export const KanbanBoardViewSchema = z.looseObject({ columns: z.array(KanbanColumnSchema).optional(), assignees: z.array(Json).optional(), filters: Json.optional(), latest_event_id: z.number().optional(), read_only: z.boolean().optional(), tenants: z.array(Json).optional(), changed: z.boolean().optional() });
506
- export const KanbanBoardsViewSchema = z.looseObject({ boards: z.array(z.looseObject({ slug: z.string(), name: z.string().nullable().optional(), is_current: z.boolean().optional(), total: z.number().optional(), archived: z.boolean().optional(), color: z.string().optional(), icon: z.string().optional(), description: z.string().optional() })), current: z.string().optional(), read_only: z.boolean().optional() });
698
+ /** One profile lane (TAL-567): an assignee's tasks (`null` = unassigned) across every board column, with its task count. */
699
+ export const KanbanLaneSchema = z.object({ assignee: z.string().nullable(), count: z.number().int(), columns: z.array(KanbanColumnSchema) });
700
+ export const KanbanBoardViewSchema = z.looseObject({ columns: z.array(KanbanColumnSchema).optional(), assignees: z.array(Json).optional(), filters: Json.optional(), latest_event_id: z.number().optional(), read_only: z.boolean().optional(), tenants: z.array(Json).optional(), changed: z.boolean().optional(),
701
+ /** Statuses a bulk status change may target (TAL-557). */
702
+ bulk_move_targets: z.array(z.string()),
703
+ /** The saved board view (TAL-567): `true` groups the board into `lanes` by assignee. */
704
+ lane_by_profile: z.boolean(),
705
+ /** Present when `lane_by_profile`: the columns split into profile lanes, assignees in name order, unassigned last. */
706
+ lanes: z.array(KanbanLaneSchema).optional() });
707
+ export const KanbanBoardsViewSchema = z.looseObject({ boards: z.array(z.looseObject({ slug: z.string(), name: z.string().nullable().optional(), is_current: z.boolean().optional(), total: z.number().optional(), archived: z.boolean().optional(), color: z.string().optional(), icon: z.string().optional(), description: z.string().optional(),
708
+ /** Whether the board can be archived or deleted; the default board cannot (TAL-567). */
709
+ removable: z.boolean() })), current: z.string().optional(), read_only: z.boolean().optional() });
710
+ /** Board-wide task counts (TAL-567): `status_counts` in board-column order, unknown statuses after; `total` excludes archived tasks. */
711
+ export const KanbanStatsViewSchema = z.looseObject({ by_status: z.record(z.string(), z.number()), total: z.number().int(), status_counts: z.array(z.object({ status: z.string(), count: z.number().int() })) });
507
712
  export const ExtensionStatusSchema = z.looseObject({
508
713
  enabled: z.boolean(), extension_dir_configured: z.boolean().optional(), extension_dir_valid: z.boolean().optional(), script_urls: z.array(z.string()).optional(), stylesheet_urls: z.array(z.string()).optional(), sidecars: z.array(Json).optional(),
509
714
  extensions: z.array(z.looseObject({ id: z.string(), enabled: z.boolean().optional(), name: z.string().optional(), version: z.string().optional() })).optional(), warnings: z.array(Json).optional(),