@maudecode/talaria-web-contracts 1.16.1 → 1.18.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.
Files changed (66) hide show
  1. package/dist/index.d.ts +1 -0
  2. package/dist/index.js +1 -0
  3. package/dist/index.js.map +1 -1
  4. package/dist/openapi.js +5 -1
  5. package/dist/openapi.js.map +1 -1
  6. package/dist/router.d.ts +4169 -1107
  7. package/dist/routes/automation.d.ts +173 -10
  8. package/dist/routes/automation.js +17 -9
  9. package/dist/routes/automation.js.map +1 -1
  10. package/dist/routes/chat.d.ts +1215 -16
  11. package/dist/routes/chat.js +5 -3
  12. package/dist/routes/chat.js.map +1 -1
  13. package/dist/routes/core.d.ts +11 -0
  14. package/dist/routes/core.js +3 -0
  15. package/dist/routes/core.js.map +1 -1
  16. package/dist/routes/git.d.ts +98 -2
  17. package/dist/routes/git.js +20 -6
  18. package/dist/routes/git.js.map +1 -1
  19. package/dist/routes/raw.d.ts +3 -1
  20. package/dist/routes/raw.js +31 -7
  21. package/dist/routes/raw.js.map +1 -1
  22. package/dist/routes/sessions.d.ts +3080 -539
  23. package/dist/routes/sessions.js +68 -1
  24. package/dist/routes/sessions.js.map +1 -1
  25. package/dist/routes/settings.d.ts +132 -2
  26. package/dist/routes/settings.js +4 -4
  27. package/dist/routes/settings.js.map +1 -1
  28. package/dist/routes/tools.d.ts +425 -19
  29. package/dist/routes/tools.js +23 -4
  30. package/dist/routes/tools.js.map +1 -1
  31. package/dist/routes/workspaces.d.ts +96 -0
  32. package/dist/routes/workspaces.js +18 -1
  33. package/dist/routes/workspaces.js.map +1 -1
  34. package/dist/sidecar/methods.d.ts +253 -0
  35. package/dist/sidecar/methods.js +2 -1
  36. package/dist/sidecar/methods.js.map +1 -1
  37. package/dist/sidecar/namespaces.d.ts +381 -1
  38. package/dist/sidecar/namespaces.js +89 -10
  39. package/dist/sidecar/namespaces.js.map +1 -1
  40. package/dist/sidecar/version.d.ts +1 -1
  41. package/dist/sidecar/version.js +1 -1
  42. package/dist/sidecar/version.js.map +1 -1
  43. package/dist/sse.d.ts +79 -14
  44. package/dist/sse.js +13 -6
  45. package/dist/sse.js.map +1 -1
  46. package/dist/theme.d.ts +10 -0
  47. package/dist/theme.js +10 -0
  48. package/dist/theme.js.map +1 -0
  49. package/dist/views.d.ts +1419 -39
  50. package/dist/views.js +215 -25
  51. package/dist/views.js.map +1 -1
  52. package/fixtures/sidecar/checkpoints.json +50 -0
  53. package/fixtures/sidecar/commands.json +250 -96
  54. package/fixtures/sidecar/cron.json +38 -55
  55. package/fixtures/sidecar/goals.json +6 -6
  56. package/fixtures/sidecar/kanban.json +84 -67
  57. package/fixtures/sidecar/plugins.json +171 -172
  58. package/fixtures/sidecar/process.json +25 -0
  59. package/fixtures/sidecar/profiles.json +10 -2
  60. package/fixtures/sidecar/providers.json +25 -12
  61. package/fixtures/sidecar/rpc.json +17 -0
  62. package/fixtures/sidecar/runtime.json +22 -29
  63. package/fixtures/sidecar/state_db.json +15 -0
  64. package/fixtures/sidecar/text.json +2 -2
  65. package/fixtures/sidecar/usage.json +38 -0
  66. package/package.json +1 -1
package/dist/views.js CHANGED
@@ -5,6 +5,7 @@
5
5
  * that must survive a round trip (documented passthrough).
6
6
  */
7
7
  import { z } from 'zod';
8
+ import { CanonicalBotChatSchema } from './sidecar/namespaces.js';
8
9
  const Json = z.unknown();
9
10
  export const NullableString = z.string().nullable();
10
11
  export const NullableNumber = z.number().nullable();
@@ -17,8 +18,28 @@ export const AttachmentSchema = z.looseObject({
17
18
  });
18
19
  /** The server's display class for a tool call: clients map it to an icon and localized verb, and never classify names themselves. */
19
20
  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() };
21
+ /**
22
+ * TAL-604: one row of a unified diff hunk as the server parsed it. `text` is the raw line, its `+` / `-` / space prefix kept;
23
+ * `old_line` / `new_line` number it on each side (null on the side it is absent from, and on a `\ No newline` marker).
24
+ */
25
+ export const DiffLineSchema = z.object({ kind: z.enum(['addition', 'deletion', 'context']), old_line: z.number().int().nullable(), new_line: z.number().int().nullable(), text: z.string() });
26
+ /**
27
+ * TAL-604: one hunk of a unified diff: its `@@` `header`, the first old and new line numbers, the last new line it covers
28
+ * (`new_end`, at least `new_start`), its added and removed line counts, and its lines. A diff without `@@` headers yields
29
+ * one hunk per file with an empty `header` and null numbers.
30
+ */
31
+ export const DiffHunkSchema = z.object({
32
+ header: z.string(), old_start: z.number().int().nullable(), new_start: z.number().int().nullable(), new_end: z.number().int().nullable(),
33
+ additions: z.number().int(), deletions: z.number().int(), lines: z.array(DiffLineSchema),
34
+ });
35
+ /**
36
+ * TAL-448: a completed file-edit call's change, from its result's unified diff: `added` / `removed` count the whole diff's
37
+ * lines (file headers excluded); `diff` is the redacted diff, capped at 400 lines, with `truncated` when cut, and `hunks`
38
+ * that diff parsed (TAL-604). Absent on a call whose result has no diff.
39
+ */
40
+ export const ToolEditDiffSchema = z.object({ added: z.number().int(), removed: z.number().int(), diff: z.string(), truncated: z.boolean(), hunks: z.array(DiffHunkSchema) });
41
+ /** 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`. */
42
+ export const ToolDisplayFields = { kind: ToolKindSchema.optional(), target: z.string().optional(), edit_diff: ToolEditDiffSchema.optional() };
22
43
  /**
23
44
  * TAL-315: a tool result's display sections, decided by the server's one rule for live, replayed and persisted results
24
45
  * alike. Clients show `stdout`, `stderr`, a labelled `error` and a labelled `exit_code` (sent only when worth showing), or
@@ -109,6 +130,14 @@ export const MessageSchema = z.looseObject({
109
130
  provider_details: Json.optional(), provider_details_label: z.string().optional(), recovery_control: Json.optional(), _anchor_activity_scene: ActivitySceneSchema.optional(),
110
131
  /** The turn this row belongs to; the server stamps every row it sends, so clients group turns by equality alone. */
111
132
  _turn_id: z.string().optional(),
133
+ /** On a turn's last assistant row: seconds the turn ran. */
134
+ _turnDuration: z.number().optional(),
135
+ /** On a turn's last assistant row: output tokens per second, when the turn reported usage. */
136
+ _turnTps: z.number().optional(),
137
+ /** Epoch seconds the row was written; `timestamp` when absent. */
138
+ _ts: z.number().optional(),
139
+ /** Reasoning titles an older Web stored on the row, in order. */
140
+ reasoning_titles: z.array(z.string()).optional(),
112
141
  /** The running turn's prompt, which the turn's settlement replaces with its persisted row. */
113
142
  _active_turn_user: z.boolean().optional(),
114
143
  /** A consumed steer at its causal place in the turn: display-only, never model history. A steer only the Agent recorded has no timing. */
@@ -118,6 +147,11 @@ export const MessageSchema = z.looseObject({
118
147
  /** A settled body too long to lay out whole: clients render `_display_excerpt` collapsed, with a local "Show more" for `content`. */
119
148
  _display_truncated: z.boolean().optional(),
120
149
  _display_excerpt: z.string().optional(),
150
+ /**
151
+ * TAL-452: a typed user message (never a steer, background update or marker) longer than 20 lines or 2,000
152
+ * characters. Clients show it folded to a few lines with a local "Show more"; copy and edit use the whole `content`.
153
+ */
154
+ _collapsible: z.boolean().optional(),
121
155
  /**
122
156
  * TAL-186: an assistant row's text with each media reference the server serves rewritten to standard Markdown: an
123
157
  * image to `![alt](url)`, any other file to `[name](url)`. Clients render it in place of `content` as one Markdown
@@ -151,6 +185,12 @@ export const MessageSchema = z.looseObject({
151
185
  _background_reply: z.boolean().optional(),
152
186
  /** TAL-460: that reply is only a silence marker; clients show the update's lines and nothing of this turn's reply. */
153
187
  _background_silent: z.boolean().optional(),
188
+ /**
189
+ * TAL-600: how clients show the row, on every row the server sends: `row` on its own, `in_scene` only inside its turn's
190
+ * activity scene (a tool result or a consumed steer), `hidden` not at all (a silent background reply, or a row with
191
+ * nothing to show).
192
+ */
193
+ _display: z.enum(['row', 'hidden', 'in_scene']).optional(),
154
194
  });
155
195
  export const ComposerDraftSchema = z.looseObject({ text: z.string().optional(), files: z.array(Json).optional() });
156
196
  /**
@@ -179,6 +219,7 @@ const ReadOnlySchema = z.boolean().describe('The session cannot be modified from
179
219
  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.');
180
220
  const CanPinSchema = z.boolean().describe('Web may pin or unpin this session: never a subagent child.');
181
221
  const CanArchiveSchema = z.boolean().describe('Web may archive or unarchive this session: never a subagent child.');
222
+ 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).');
182
223
  const CanDuplicateSchema = z.boolean().describe('Web may duplicate this session: it has a WebUI copy and is not a subagent child.');
183
224
  // TAL-310: the server classifies every session's source once; clients file rows by it and never scan source markers.
184
225
  export const SourceKindSchema = z.enum(['webui', 'cli', 'messaging', 'cron', 'webhook', 'subagent', 'claude_code', 'kanban', 'api', 'other'])
@@ -191,6 +232,17 @@ const IsMessagingSessionSchema = z.boolean().describe('`source_kind` is `messagi
191
232
  * the folder name (TAL-303). Null without a workspace; absent from an older server, where clients show no label.
192
233
  */
193
234
  const WorkspaceNameSchema = NullableString.optional();
235
+ /**
236
+ * TAL-560: the "Context compaction · Reference only" card, on payloads that carry `messages`. Clients render `text` after
237
+ * the row at full-transcript index `after_message_index` (row `after_message_index - _messages_offset` of `messages`), or
238
+ * above the loaded rows when it is null or before them. Null: no card.
239
+ */
240
+ export const CompressionReferenceSchema = z.object({
241
+ text: z.string(),
242
+ 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.'),
243
+ });
244
+ /** TAL-454: the chat a `/branch` copied, with its redacted title. */
245
+ export const BranchedFromSchema = z.object({ session_id: SessionIdSchema, title: z.string() });
194
246
  /** Full session record from `GET /api/session` and mutations returning `session`. */
195
247
  export const SessionSchema = z.looseObject({
196
248
  session_id: SessionIdSchema, title: z.string(), workspace: z.string().optional(), created_workspace: z.string().nullable().optional(), model: NullableString.optional(), model_provider: NullableString.optional(),
@@ -201,9 +253,10 @@ export const SessionSchema = z.looseObject({
201
253
  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(),
202
254
  pending_user_message: NullableString.optional(), pending_attachments: z.array(AttachmentSchema).optional(), pending_started_at: NullableNumber.optional(), pending_user_source: NullableString.optional(),
203
255
  context_length: NullableNumber.optional(), threshold_tokens: NullableNumber.optional(), last_prompt_tokens: NullableNumber.optional(), post_compression_context_tokens_estimate: NullableNumber.optional(), ...ContextUsageFields,
204
- 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_duplicate: CanDuplicateSchema, source_tag: NullableString.optional(),
256
+ 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(),
257
+ can_compress: z.boolean().optional().describe('TAL-614: a manual compression would start now: writable, no running turn, and at least four model-history messages. Only the full session detail carries it; absent means no.'),
205
258
  source_label: NullableString.optional(), session_source: NullableString.optional(), raw_source: NullableString.optional(), parent_session_id: NullableString.optional(), worktree_path: NullableString.optional(),
206
- worktree_branch: NullableString.optional(), worktree_repo_root: NullableString.optional(), share_token: NullableString.optional(), share_created_at: NullableNumber.optional(), manual_title: z.boolean().optional(),
259
+ 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(),
207
260
  compression_anchor_summary: NullableString.optional(), compression_recovery: z.record(z.string(), Json).optional(), recommended_recovery_action: NullableString.optional(), compression_recovery_action: NullableString.optional(),
208
261
  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(),
209
262
  /**
@@ -215,15 +268,17 @@ export const SessionSchema = z.looseObject({
215
268
  /** The agent's display name: a named profile's own name, else the `bot_name` setting (TAL-458). */
216
269
  assistant_name: z.string().optional(),
217
270
  workspace_name: WorkspaceNameSchema,
271
+ compression_reference: CompressionReferenceSchema.nullable().optional(),
272
+ branched_from: BranchedFromSchema.nullable().optional().describe('TAL-454: the chat this one was branched from while it still loads (archived included); null for a compression continuation, a deleted or missing parent, and every other chat.'),
218
273
  });
219
274
  export const SessionEnvelopeSchema = z.looseObject({ session: SessionSchema });
220
275
  /** Sidebar row from `GET /api/sessions`. */
221
276
  export const SessionRowSchema = z.looseObject({
222
277
  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(),
223
278
  message_count: z.number().optional(), pinned: z.boolean().optional(), archived: z.boolean().optional(), project_id: NullableString.optional(), profile: NullableString.optional(), is_streaming: IsStreamingSchema,
224
- 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_duplicate: CanDuplicateSchema, attention: z.looseObject({ kind: z.string().optional(), count: z.number().optional() }).nullable().optional(),
279
+ 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(),
225
280
  source_tag: NullableString.optional(), source_label: NullableString.optional(), session_source: NullableString.optional(), raw_source: NullableString.optional(), parent_session_id: NullableString.optional(),
226
- active_stream_id: ActiveStreamIdSchema, share_token: NullableString.optional(), worktree_branch: NullableString.optional(), match_type: z.enum(['title', 'metadata', 'content']).optional(), match_preview: NullableString.optional(),
281
+ 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(),
227
282
  workspace_name: WorkspaceNameSchema,
228
283
  ...ContextUsageFields,
229
284
  });
@@ -254,7 +309,12 @@ export const ChatStartResponseSchema = z.looseObject({
254
309
  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(),
255
310
  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(),
256
311
  });
257
- export const StreamStatusSchema = z.looseObject({ active: z.boolean(), stream_id: z.string(), replay_available: z.boolean().optional(), journal: Json.optional() });
312
+ /**
313
+ * `active` is whether this stream's channel is still live. `blocking_stream_id` (TAL-622) is the run that still holds the
314
+ * stream's session: this run while it unwinds after a Stop, or a successor such as a goal continuation. A session delete
315
+ * answers 409 while it is set, so a client draining a session before deleting it waits until it is null.
316
+ */
317
+ 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() });
258
318
  /**
259
319
  * TAL-424: a pending steer is no longer pending without the Agent taking it: Edit and Cancel by the user, `stopped` by a
260
320
  * Stop (`text` goes back to the composer), or `followup` when the server sends it as the next turn. `steer_id` is null
@@ -301,11 +361,11 @@ export const ClarifyRespondRequestSchema = z.looseObject({ session_id: SessionId
301
361
  export const ClarifyRespondResponseSchema = z.looseObject({ ok: z.boolean(), response: z.string().optional(), error: z.string().optional(), stale: z.boolean().optional() });
302
362
  export const DraftSchema = z.looseObject({ text: z.string(), files: z.array(Json) });
303
363
  export const DraftResponseSchema = z.looseObject({ ok: z.literal(true), draft: DraftSchema, draft_version: z.string().nullable(), unchanged: z.boolean().optional() });
304
- 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() });
364
+ /** At most this many attachments ride one chat message; the server names each in the prompt (TAL-276, TAL-635). */
365
+ export const MAX_CHAT_ATTACHMENTS = 20;
366
+ 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() });
305
367
  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() });
306
368
  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() });
307
- 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() });
308
- export const BackgroundStatusSchema = z.looseObject({ results: z.array(BackgroundResultSchema) });
309
369
  /**
310
370
  * TAL-372: one piece of background work a session owns, the same record for every client, reload and restart.
311
371
  * - `task_id` is stable: the delegation unit's id, the process id, or the `/background` task id.
@@ -352,6 +412,11 @@ export const SessionNewRequestSchema = z.looseObject({
352
412
  });
353
413
  export const TranscribeCapabilitySchema = z.looseObject({ ok: z.literal(true), available: z.boolean(), provider: z.string() });
354
414
  // ── settings, profiles, models, providers ───────────────────────────────
415
+ /** TAL-411: the request profile's quota urgency thresholds. A save patches valid fields and clamps critical to at most warning. */
416
+ export const QuotaThresholdsSchema = z.object({
417
+ 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),
418
+ 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),
419
+ });
355
420
  export const LoginResponseSchema = z.looseObject({ ok: z.literal(true), message: z.string().optional() });
356
421
  export const SettingsSchema = z.looseObject({
357
422
  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(),
@@ -364,10 +429,15 @@ export const SettingsSchema = z.looseObject({
364
429
  dictation_append: z.boolean().optional(), persisted_speech_keys: z.array(z.string()).optional(),
365
430
  /** TAL-279: ask before opening external chat links; `trusted_link_hosts` are exact, server-normalized hostnames that skip the ask. */
366
431
  confirm_external_links: z.boolean().optional(), trusted_link_hosts: z.array(z.string()).optional(),
432
+ provider_quota_thresholds: QuotaThresholdsSchema.optional(),
367
433
  });
368
434
  export const ProfileSchema = z.looseObject({
369
435
  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(),
370
436
  total_skills: z.number().optional(), enabled_skills: z.number().optional(), gateway_running: z.boolean().optional(), has_env: z.boolean().optional(), visible: z.boolean().optional(),
437
+ /** TAL-213: Bot Mode identity from `profile.yaml` (the bot's roster title, else the profile's display name), one bounded printable line each. */
438
+ display_name: z.string().optional(), description: z.string().optional(), has_avatar: z.boolean().optional(),
439
+ /** TAL-213: present only when the profile has a canonical Bot Chat; clients open `tip_session_id` after switching to the profile. */
440
+ canonical_session: CanonicalBotChatSchema.nullable().optional(),
371
441
  });
372
442
  export const ProfilesSchema = z.looseObject({ profiles: z.array(ProfileSchema), active: z.string(), single_profile_mode: z.boolean().optional() });
373
443
  export const ActiveProfileSchema = z.looseObject({ name: z.string(), path: z.string().optional(), is_default: z.boolean().optional(), default_workspace: NullableString.optional() });
@@ -378,16 +448,29 @@ export const ReasoningStatusSchema = z.looseObject({ show_reasoning: z.boolean()
378
448
  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() });
379
449
  export const ModelGroupSchema = z.looseObject({ provider: z.string(), provider_id: z.string().optional(), models: z.array(ModelEntrySchema), extra_models: z.array(ModelEntrySchema).optional() });
380
450
  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() });
451
+ /** TAL-603: where a card's credential comes from; `other` is a source the server cannot name, shown as its raw `key_source`. */
452
+ export const KeySourceKindSchema = z.enum(['env', 'oauth', 'config', 'pool', 'plugin', 'other']);
453
+ /**
454
+ * `has_key` reports a credential; `configured` (TAL-570) is the row's setup status, which a keyless Ollama or LM Studio endpoint also meets.
455
+ * TAL-603: `is_active` marks the active provider's card; `key_source_kind` is the key badge (`null` without a credential);
456
+ * `models_total` is the catalog size, never below `models.length`. `is_active` and `key_source_kind` are absent from older servers.
457
+ * TAL-641: `removable` marks a key Remove would clear: a built-in, not self-hosted, provider whose only key is its own profile `.env` variable.
458
+ */
381
459
  export const ProviderSchema = z.looseObject({
382
- 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(),
460
+ 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(),
383
461
  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(),
462
+ is_active: z.boolean().optional(), key_source_kind: KeySourceKindSchema.nullable().optional(), removable: z.boolean().optional(),
384
463
  });
385
464
  export const ProvidersSchema = z.looseObject({ providers: z.array(ProviderSchema), active_provider: NullableString.optional() });
465
+ export const QuotaLevelSchema = z.enum(['healthy', 'warning', 'critical', 'stale', 'unavailable']);
466
+ /** TAL-411: the server's classification per colour basis; `/api/provider/quotas` only, at the profile's thresholds. */
467
+ export const QuotaUrgencySchema = z.looseObject({ remaining: QuotaLevelSchema, pace: QuotaLevelSchema.describe('Burn rate and pace tolerance; the remaining classification when the window has no pace.') });
386
468
  /** TAL-409: a window's pace as of the envelope's `computed_at`. Stale once `valid_until` (the window's reset) passes. */
387
469
  export const QuotaPaceSchema = z.looseObject({
388
470
  expected_remaining_percent: z.number(), pace_delta_percent: z.number(), burn_rate: z.number(), minutes_to_reset: z.number(),
389
471
  projected_minutes_to_empty: NullableNumber.describe('Minutes until the window empties at the current burn; null when nothing has been used.'),
390
472
  elapsed_minutes: z.number(), valid_until: z.string(),
473
+ 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.'),
391
474
  });
392
475
  export const QuotaForecastSchema = z.looseObject({
393
476
  outcome: z.enum(['safe', 'warning']).describe('`warning` when the projection empties the window before it resets.'),
@@ -399,6 +482,8 @@ export const QuotaWindowSchema = z.looseObject({
399
482
  window_seconds: NullableNumber.describe('The provider value, else 5h or weekly from the label; null when unknown.'),
400
483
  pace: QuotaPaceSchema.nullable().describe('Null for a window without a future reset, a usage value, or a 5h/weekly length.'),
401
484
  forecast: QuotaForecastSchema.nullable(),
485
+ 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).'),
486
+ urgency: QuotaUrgencySchema.optional(),
402
487
  });
403
488
  const QuotaWindowIndex = z.number().int().nullable().optional();
404
489
  export const QuotaSourceSchema = z.looseObject({
@@ -406,6 +491,7 @@ export const QuotaSourceSchema = z.looseObject({
406
491
  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(),
407
492
  pace_window_index: QuotaWindowIndex.describe('The window a pace-coloured widget shows: the weekly one, else the first 5h/session window.'),
408
493
  session_window_index: QuotaWindowIndex.describe('The session (else 5h) window.'), weekly_window_index: QuotaWindowIndex.describe('The weekly window.'),
494
+ urgency: QuotaUrgencySchema.optional().describe('TAL-411: `remaining` of the first window, `pace` of the pace window; alerts and the source colour follow it.'),
409
495
  });
410
496
  /** An account-usage provider's normalised snapshot on `/api/provider/quota` (the same windows and indexes as a quotas source). */
411
497
  export const QuotaAccountLimitsSchema = z.looseObject({
@@ -426,6 +512,22 @@ export const ProviderQuotasSchema = z.looseObject({
426
512
  missing_source: z.boolean().describe('True when `?source=` names a source id this scope no longer has; `sources` is then empty.'),
427
513
  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.'),
428
514
  });
515
+ /** One day of OpenRouter spend on `/api/provider/cost-history` (TAL-412). */
516
+ export const CostSnapshotSchema = z.looseObject({
517
+ date: z.string().describe('UTC day, `YYYY-MM-DD`.'), used: NullableNumber.describe('Cumulative OpenRouter usage in USD that day.'),
518
+ 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`."),
519
+ 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.'),
520
+ });
521
+ /** Python `get_provider_cost_history`: OpenRouter daily spend with the server-computed monthly pace and budget standing. */
522
+ export const ProviderCostHistorySchema = z.looseObject({
523
+ 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(),
524
+ window_days: z.number().int().optional(), snapshots: z.array(CostSnapshotSchema).optional(), limit: NullableNumber.optional(), label: NullableString.optional(),
525
+ monthly_budget: NullableNumber.optional().describe('Settings `provider_cost_budget`.'),
526
+ monthly_pace: NullableNumber.optional().describe('Mean of the non-null deltas × 30, in USD rounded to cents; null without a delta.'),
527
+ has_enough_data: z.boolean().optional().describe('At least one non-null delta (two daily snapshots).'),
528
+ budget_percent: NullableNumber.optional().describe('round(pace / budget × 100); null without a positive pace and a budget.'),
529
+ budget_level: z.enum(['ok', 'warn', 'over']).nullable().optional().describe('`warn` from 80%, `over` from 100%; null without `budget_percent`.'),
530
+ });
429
531
  export const PersonalitiesSchema = z.looseObject({ personalities: z.array(z.looseObject({ name: z.string(), description: z.string().optional() })) });
430
532
  /** One auxiliary task slot in server order (TAL-388). The server owns ordering, normalization, and catalog matching. */
431
533
  export const AuxiliaryTaskSchema = z.looseObject({
@@ -453,12 +555,25 @@ export const FileEntrySchema = z.looseObject({
453
555
  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(),
454
556
  });
455
557
  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() });
456
- 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() });
457
- 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() });
558
+ /** How a workspace file renders (TAL-566): media streams from `/api/file/raw`; text kinds carry `content`. */
559
+ export const FilePreviewKindSchema = z.enum(['text', 'markdown', 'csv', 'html', 'image', 'pdf', 'audio', 'video', 'binary']);
560
+ /** `table` is a `csv` preview's leading rows (header first); `table_truncated` says the file has more. `preview_url` is an
561
+ * `html` preview's app-relative frame URL, under which the page's relative assets also resolve. */
562
+ export const FileContentSchema = z.looseObject({
563
+ 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(),
564
+ preview: FilePreviewKindSchema.optional(), table: z.array(z.array(z.string())).optional(), table_truncated: z.boolean().optional(),
565
+ preview_url: z.string().optional(),
566
+ });
567
+ const GitBadgeSchema = 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() });
568
+ export const GitInfoSchema = z.looseObject({ git: GitBadgeSchema.nullable().optional() });
458
569
  // ── tools ────────────────────────────────────────────────────────────────
459
570
  export const SkillSchema = z.looseObject({ name: z.string(), description: NullableString.optional(), category: NullableString.optional(), disabled: z.boolean().optional() });
460
571
  export const SkillsSchema = z.looseObject({ skills: z.array(SkillSchema), categories: z.array(Json).optional() });
461
- export const SkillContentSchema = z.looseObject({ name: z.string().optional(), content: z.string().optional(), path: z.string().optional(), success: z.boolean().optional(), message: z.string().optional(), ok: z.boolean().optional() });
572
+ /**
573
+ * `linked_files` groups the skill directory's files by folder (`references`, `templates`, ...); open one with `?file=`.
574
+ * A linked file that is not UTF-8 text carries `binary: true` and its `size` instead of `content`.
575
+ */
576
+ export const SkillContentSchema = z.looseObject({ name: z.string().optional(), content: z.string().optional(), path: z.string().optional(), linked_files: z.record(z.string(), z.array(z.string())).optional(), binary: z.literal(true).optional(), size: z.number().optional(), success: z.boolean().optional(), message: z.string().optional(), ok: z.boolean().optional() });
462
577
  export const SkillsUsageSchema = z.looseObject({ usage: z.record(z.string(), z.looseObject({ use_count: z.number().optional(), view_count: z.number().optional(), patch_count: z.number().optional() })), skill_names: z.array(z.string()).optional(), total_invocations: z.number().optional(), unique_skills_used: z.number().optional() });
463
578
  export const MemorySchema = z.looseObject({
464
579
  memory: z.string(), user: z.string(), soul: z.string(), project_context: z.string().optional(), memory_path: z.string().optional(), user_path: z.string().optional(), soul_path: z.string().optional(), project_context_path: z.string().optional(),
@@ -472,13 +587,14 @@ export const PromptsSchema = z.looseObject({ prompts: z.array(PromptSchema) });
472
587
  * first, then the Agent registry, in display order; each name appears once. `handler` says who runs it, `clients` which
473
588
  * clients can run it, and `unsupported_message` is the English text a client outside `clients` shows when it is typed.
474
589
  * Clients suggest the entries listing them whose name or any alias starts with the typed text (case-insensitive), and
475
- * resolve a typed alias to the entry's `name`.
590
+ * resolve a typed alias to the entry's `name`. `exec` marks an Agent entry `POST /api/commands/exec` runs: the client
591
+ * posts the typed text there and shows the returned output instead of starting a chat turn (TAL-561).
476
592
  */
477
593
  export const CommandClientSchema = z.enum(['web', 'ios']);
478
594
  export const CommandRowSchema = z.looseObject({
479
595
  name: z.string(), description: z.string().optional(), aliases: z.array(z.string()), args_hint: z.string().optional(), category: z.string().optional(),
480
596
  handler: z.enum(['client', 'agent']), clients: z.array(CommandClientSchema), unsupported_message: z.string().optional(),
481
- cli_only: z.boolean().optional(), gateway_only: z.boolean().optional(), subcommands: z.array(Json).optional(),
597
+ cli_only: z.boolean().optional(), gateway_only: z.boolean().optional(), subcommands: z.array(Json).optional(), exec: z.boolean().optional(),
482
598
  });
483
599
  export const CommandsSchema = z.looseObject({ commands: z.array(CommandRowSchema) });
484
600
  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() });
@@ -489,14 +605,52 @@ export const InsightsSchema = z.looseObject({
489
605
  models: z.array(z.looseObject({ model: z.string().optional(), sessions: z.number().optional(), tokens: z.number().optional(), cost: z.number().optional() })).optional(),
490
606
  });
491
607
  export const DashboardStatusSchema = z.looseObject({ running: z.boolean(), enabled: z.string().optional(), url: z.string().optional(), browser_url: z.string().optional(), host: z.string().optional(), port: z.number().int().optional(), version: z.string().optional(), error: z.string().optional() });
608
+ /**
609
+ * Gateway status for the Settings banner (TAL-266). `running` and `configured` come from the Agent health probe; when it
610
+ * is inconclusive, stale running metadata or known gateway sessions mark the gateway configured. `platforms` are the
611
+ * messaging platforms of known gateway sessions; `last_active` is the gateway session index's ISO mtime while running, else "".
612
+ */
613
+ export const GatewayStatusSchema = z.object({
614
+ running: z.boolean(), configured: z.boolean(), platforms: z.array(z.object({ name: z.string(), label: z.string() })), last_active: z.string(), session_count: z.number().int(),
615
+ health: z.object({ state: NullableString, reason: NullableString, gateway_state: NullableString }),
616
+ });
617
+ export const GatewayActionSchema = z.object({ ok: z.literal(true), action: z.enum(['start', 'stop', 'restart']), message: z.string(), status: GatewayStatusSchema });
618
+ export const DashboardConfigSchema = z.object({ enabled: z.enum(['auto', 'always', 'never']), url: z.string() });
619
+ /**
620
+ * Project OS dashboard for a repo found from the last workspace or the `board`'s default workdir (TAL-266). Every field is
621
+ * null or empty when the profile has no local workspace I/O or no repo is found. `onboarding.active` means the docs came
622
+ * from the repo root's `PROJECT.md`/`PLAN.md`/`STATUS.md` instead of `docs/project-os/`.
623
+ */
624
+ export const ProjectOsDashboardSchema = z.object({
625
+ workspace: NullableString, repo_root: NullableString, selected_board_slug: NullableString.optional(), git: GitBadgeSchema.nullable(),
626
+ docs: z.object({ project: FileContentSchema.nullable(), plan: FileContentSchema.nullable(), status: FileContentSchema.nullable(), blocker_resolver: FileContentSchema.nullable() }).partial(),
627
+ handoff: z.record(z.string(), Json).nullable(), active: z.record(z.string(), Json).nullable(), heartbeat: z.record(z.string(), Json).nullable(),
628
+ onboarding: z.looseObject({ active: z.boolean(), doc_source: z.enum(['project-os', 'root']) }).optional(), goal_summary: z.string(),
629
+ });
492
630
  export const AgentHealthSchema = z.looseObject({ alive: z.boolean().nullable().optional(), checked_at: z.string().optional(), details: z.looseObject({ state: z.string().optional(), reason: z.string().optional() }).optional(), gateway_chat: z.looseObject({ enabled: z.boolean().optional(), backend: z.string().optional(), base_url_configured: z.boolean().optional(), api_key_configured: z.boolean().optional() }).optional(), error: z.string().optional() });
493
631
  export const SystemHealthSchema = z.looseObject({
494
632
  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(),
495
633
  errors: z.array(z.looseObject({ code: z.string().optional(), metric: z.string().optional() })).optional(), webui_runtime: Json.optional(),
496
634
  });
497
- 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() });
635
+ export const UpdateStateSchema = z.enum(['up_to_date', 'release_ready', 'commits_behind', 'local_changes', 'finish', 'manual', 'check_failed', 'off', 'unknown']);
636
+ 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(),
637
+ state: UpdateStateSchema.describe('TAL-559: the status clients word; `off` when checks are disabled or Agent updates are ignored.'),
638
+ can_apply: z.boolean().describe('The apply action is available for this target.'),
639
+ installed_unverified: z.boolean().describe('`release_ready` only: the installed revision sits on no release tag.'),
640
+ manual_link: z.boolean().describe('Show the manual-install (releases) link.'),
641
+ target_version: z.string().nullable().describe('The version the update path leads to, when one is shown.'),
642
+ summary_available: z.boolean().describe('TAL-759: offer "Summarize with AI" for this target (`release_ready` or `commits_behind`).'),
643
+ });
498
644
  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() });
499
- 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() });
645
+ export const UpdatesSummarySchema = z.object({
646
+ ok: z.boolean(), target: z.enum(['webui', 'agent']), summary: z.string(),
647
+ current_version: z.string().nullable().describe('The installed version this summary starts from.'),
648
+ target_version: z.string().nullable().describe('The version this summary leads to, when the update names one.'),
649
+ summary_sections: z.array(z.object({ title: z.string(), items: z.array(z.string()) })).describe('Display-ready headed bullet lists; empty when nothing is pending.'),
650
+ generated_by: z.enum(['llm', 'fallback', 'none']).describe('`fallback` when no model answered; `none` when the target has no pending update to summarize.'),
651
+ source: z.enum(['release_notes', 'commits', 'none']).describe('Stable targets summarize the release notes they install; commit-tracking targets, or unreadable notes, use commit subjects.'),
652
+ cached: z.boolean(),
653
+ });
500
654
  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() });
501
655
  export const UpdateNotificationPhaseSchema = z.enum(['applying', 'awaiting_confirmation', 'restarting', 'succeeded', 'blocked', 'failed', 'unknown']);
502
656
  export const UpdateNotificationActionSchema = z.object({
@@ -558,12 +712,24 @@ export const UpdateNotificationsSchema = z.object({
558
712
  });
559
713
  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() });
560
714
  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() });
561
- 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() });
715
+ 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() });
562
716
  export const McpServersSchema = z.looseObject({ servers: z.array(McpServerSchema), health_pending: z.boolean().optional(), reload_required: z.boolean().optional(), toggle_supported: z.boolean().optional() });
563
717
  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() });
564
718
  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() });
719
+ /** Joplin search hit: bounded, redacted display text and a snippet around the query; `updated_time` is Joplin's raw value. */
720
+ export const NoteSearchResultSchema = z.object({ id: z.string(), title: z.string(), snippet: z.string(), parent_id: z.string(), updated_time: Json, source: z.literal('joplin') });
721
+ export const NotesSearchSchema = z.object({ source: z.literal('joplin'), query: z.string(), results: z.array(NoteSearchResultSchema) });
722
+ /** One Joplin note for preview: `body` is cut at 50,000 characters (with a notice) and redacted. */
723
+ export const NoteItemSchema = z.object({ source: z.literal('joplin'), note: z.object({ id: z.string(), title: z.string(), body: z.string(), parent_id: z.string(), updated_time: Json, created_time: Json, source: z.literal('joplin') }) });
724
+ /** LLM wiki summary: counts and timestamps only, never page bodies or the wiki path. `error` names the exception type when `status` is `error`. */
725
+ export const WikiStatusSchema = z.object({
726
+ available: z.boolean(), enabled: z.boolean(), status: z.enum(['missing', 'not_directory', 'ready', 'empty', 'error']), entry_count: z.number().int(), page_count: z.number().int(), raw_source_count: z.number().int(),
727
+ last_updated: z.string().nullable(), last_writer: z.string(), path_configured: z.boolean(), path_source: z.string(), toggle_available: z.literal(false), toggle_reason: z.string(), docs_url: z.string(), error: z.string().optional(),
728
+ });
729
+ export const WikiBrowseSchema = z.object({ pages: z.array(z.object({ name: z.string(), path: z.string(), size: z.number().int(), mtime: z.number().int() })) });
730
+ export const WikiPageSchema = z.object({ content: z.string(), path: z.string() });
565
731
  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() });
566
- 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() });
732
+ 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() });
567
733
  // ── onboarding ───────────────────────────────────────────────────────────
568
734
  /** `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. */
569
735
  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() });
@@ -599,8 +765,15 @@ export const CronJobViewSchema = z.looseObject({
599
765
  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(),
600
766
  /** Server-derived (TAL-296): the job's status, whether it needs attention, and whether its primary action is Resume. Live runs overlay it. */
601
767
  derived_state: CronDerivedStateSchema.optional(), needs_attention: z.boolean().optional(), resumable: z.boolean().optional(),
768
+ /** Server-derived (TAL-599): a known cause clients explain in the needs-attention banner. */
769
+ attention_hint: z.enum(['croniter_missing']).nullable(),
770
+ });
771
+ export const CronsSchema = z.looseObject({
772
+ jobs: z.array(CronJobViewSchema),
773
+ /** TAL-601: `jobs` arrive in display order (running first, then soonest next run, none last, then name); clients keep it. Absent from older servers. */
774
+ ordered: z.boolean().optional(),
775
+ active_profile: z.string().optional(), all_profiles: z.boolean().optional(), other_profile_count: z.number().optional(), cron_unavailable: z.boolean().optional(),
602
776
  });
603
- 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() });
604
777
  export const CronContextSourcesSchema = z.object({ profile: z.string(), sources: z.array(z.object({ job_id: z.string(), label: z.string(), selectable: z.boolean() })) });
605
778
  /** `GET /api/crons/recent` row: one job's latest completion, ordered and classified by the server. */
606
779
  export const CronRecentCompletionSchema = z.object({
@@ -612,12 +785,29 @@ export const CronMutationSchema = z.looseObject({ ok: z.boolean().optional(), jo
612
785
  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() });
613
786
  export const CronRunSummarySchema = z.looseObject({ filename: z.string(), size: z.number(), modified: z.number(), usage: CronRunUsageSchema.optional() });
614
787
  export const CronHistorySchema = z.looseObject({ job_id: z.string().optional(), runs: z.array(CronRunSummarySchema), total: z.number().optional(), offset: z.number().optional() });
615
- export const CronRunSchema = z.looseObject({ content: z.string().optional(), snippet: z.string().optional(), usage: CronRunUsageSchema.optional(), error: z.string().optional() });
788
+ /** `response` is server-extracted (TAL-599): the agent's reply after the run file's `## Response` heading, else the whole text. */
789
+ export const CronRunSchema = z.looseObject({ content: z.string().optional(), snippet: z.string().optional(), response: z.string().optional(), usage: CronRunUsageSchema.optional(), error: z.string().optional() });
616
790
  export const CronStatusSchema = z.looseObject({ running: z.union([z.boolean(), z.record(z.string(), z.number())]).optional() });
617
- 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() });
791
+ /** 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. */
792
+ export const KanbanTaskActionsSchema = z.object({ block: z.boolean(), unblock: z.boolean(), complete: z.boolean(), archive: z.boolean(), move_to: z.array(z.string()) });
793
+ /** 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. */
794
+ export const KanbanTaskPolicyShape = { available_actions: KanbanTaskActionsSchema, requires_running_exit_confirmation: z.boolean() };
795
+ 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 });
618
796
  export const KanbanColumnSchema = z.looseObject({ name: z.string(), tasks: z.array(KanbanTaskViewSchema) });
619
- 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() });
620
- 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() });
797
+ /** One profile lane (TAL-567): an assignee's tasks (`null` = unassigned) across every board column, with its task count. */
798
+ export const KanbanLaneSchema = z.object({ assignee: z.string().nullable(), count: z.number().int(), columns: z.array(KanbanColumnSchema) });
799
+ 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(),
800
+ /** Statuses a bulk status change may target (TAL-557). */
801
+ bulk_move_targets: z.array(z.string()),
802
+ /** The saved board view (TAL-567): `true` groups the board into `lanes` by assignee. */
803
+ lane_by_profile: z.boolean(),
804
+ /** Present when `lane_by_profile`: the columns split into profile lanes, assignees in name order, unassigned last. */
805
+ lanes: z.array(KanbanLaneSchema).optional() });
806
+ 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(),
807
+ /** Whether the board can be archived or deleted; the default board cannot (TAL-567). */
808
+ removable: z.boolean() })), current: z.string().optional(), read_only: z.boolean().optional() });
809
+ /** Board-wide task counts (TAL-567): `status_counts` in board-column order, unknown statuses after; `total` excludes archived tasks. */
810
+ 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() })) });
621
811
  export const ExtensionStatusSchema = z.looseObject({
622
812
  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(),
623
813
  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(),