@maudecode/talaria-web-contracts 1.15.0 → 1.16.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/views.js CHANGED
@@ -19,17 +19,24 @@ export const AttachmentSchema = z.looseObject({
19
19
  export const ToolKindSchema = z.enum(['shell', 'read', 'list', 'search', 'web', 'write', 'skill', 'memory', 'delegate', 'unknown']);
20
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
21
  export const ToolDisplayFields = { kind: ToolKindSchema.optional(), target: z.string().optional() };
22
+ /**
23
+ * TAL-315: a tool result's display sections, decided by the server's one rule for live, replayed and persisted results
24
+ * alike. Clients show `stdout`, `stderr`, a labelled `error` and a labelled `exit_code` (sent only when worth showing), or
25
+ * `text`, in that order, and never parse result text. Each string is capped server-side; a client without the field
26
+ * shows `preview`.
27
+ */
28
+ 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
29
  /**
23
30
  * Stored transcripts carry the OpenAI shape (`function.name` / `function.arguments` JSON string); live events carry `name` / `args`.
24
31
  * On an assistant row the server resolves every call (TAL-313, `session-sse-contract-v1.md`): Anthropic `tool_use` parts and
25
32
  * session-level-only calls join `tool_calls`; `done` is answered or outside the running turn; `is_error` is the server's
26
33
  * 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.
34
+ * result snippet, else `null`; `result_view` its display sections, else `null`. Clients render these and pair nothing themselves.
28
35
  */
29
36
  export const ToolCallSchema = z.looseObject({
30
37
  ...ToolDisplayFields,
31
38
  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(),
39
+ 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
40
  timestamp: z.number().nullable().optional(), event_type: z.string().optional(),
34
41
  });
35
42
  export const ContentPartSchema = z.looseObject({ type: z.string(), text: z.string().optional() });
@@ -46,11 +53,23 @@ export const BackgroundLinkSchema = z.object({
46
53
  status: z.enum(['running', 'attention', 'completed', 'failed', 'cancelled', 'unknown']),
47
54
  agents: z.object({ total: z.number().int(), completed: z.number().int(), failed: z.number().int(), running: z.number().int() }),
48
55
  });
56
+ /**
57
+ * TAL-186: one local or remote media reference the server recognized in a message's Markdown (a `MEDIA:` token, a bare
58
+ * `file://` URL or a local image destination), in content order. `url` is relative to the app root (`./api/media?path=…&session_id=…`)
59
+ * for a local file the `/api/media` allow-list serves, or the remote `http(s)` URL as written. Clients render `image`
60
+ * items inline from the display text and every other kind as a tile after it. A remote URL without a file
61
+ * extension has no known kind and is a `file`.
62
+ */
63
+ export const DisplayMediaSchema = z.object({ url: z.string(), name: z.string(), mime: z.string(), kind: z.enum(['image', 'audio', 'video', 'pdf', 'file']) });
49
64
  /** One normalized activity row: the server decides role, order, tool completion/error, and steering consumption. */
50
65
  export const ActivitySceneRowSchema = z.looseObject({
51
66
  row_id: z.string(), order_index: z.number().int(), role: z.enum(['prose', 'reasoning', 'tool', 'steering']), created_at: z.number().optional(),
52
67
  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(),
68
+ /** TAL-186: a prose row's `text` with its media references rewritten for display (see `_display_content`), and its media. */
69
+ display_text: z.string().optional(), media: z.array(DisplayMediaSchema).optional(),
70
+ 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(),
71
+ /** TAL-331: a limited response clipped `result` (originally `result_chars` long); `GET /api/session/tool-result` serves it whole. */
72
+ result_truncated: z.literal(true).optional(), result_chars: z.number().int().nullable().optional() }).optional(),
54
73
  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
74
  });
56
75
  /** 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 +81,10 @@ export const ActivitySceneSchema = z.looseObject({
62
81
  version: z.literal('activity_scene_v1'), activity_rows: z.array(ActivitySceneRowSchema), final_answer: z.string().optional(),
63
82
  /** TAL-456: a settled final answer too long to lay out whole; clients render it collapsed, with a local "Show more". */
64
83
  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. */
84
+ /** TAL-186: `final_answer` with its media references rewritten for display (see `_display_content`), and its media. */
85
+ final_answer_display: z.string().optional(), final_answer_media: z.array(DisplayMediaSchema).optional(),
86
+ 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.'),
87
+ /** Whether the "Worked" disclosure opens by default: an unsuccessful outcome with work to read, or a running turn. */
68
88
  expanded_by_default: z.boolean().optional(),
69
89
  /** Seconds from the turn's last consumed steer to its end, when it has steers. */
70
90
  final_phase_duration: z.number().optional(),
@@ -78,8 +98,14 @@ export const ActivitySceneSchema = z.looseObject({
78
98
  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
99
  export const MessageSchema = z.looseObject({
80
100
  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(),
101
+ attachments: z.array(AttachmentSchema).optional(), tool_calls: z.array(ToolCallSchema).optional(),
102
+ /**
103
+ * TAL-302: an assistant row's whole reasoning as one string (its reasoning fields, typed thinking parts and inline
104
+ * thinking blocks). Its `content` carries none of them and no leaked tool-call XML; `reasoning_content` and
105
+ * `thinking` are not sent. Clients render both as shipped.
106
+ */
107
+ reasoning: z.string().optional(),
108
+ 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
109
  provider_details: Json.optional(), provider_details_label: z.string().optional(), recovery_control: Json.optional(), _anchor_activity_scene: ActivitySceneSchema.optional(),
84
110
  /** The turn this row belongs to; the server stamps every row it sends, so clients group turns by equality alone. */
85
111
  _turn_id: z.string().optional(),
@@ -92,6 +118,14 @@ export const MessageSchema = z.looseObject({
92
118
  /** A settled body too long to lay out whole: clients render `_display_excerpt` collapsed, with a local "Show more" for `content`. */
93
119
  _display_truncated: z.boolean().optional(),
94
120
  _display_excerpt: z.string().optional(),
121
+ /**
122
+ * TAL-186: an assistant row's text with each media reference the server serves rewritten to standard Markdown: an
123
+ * image to `![alt](url)`, any other file to `[name](url)`. Clients render it in place of `content` as one Markdown
124
+ * document; `content` stays as stored for copy and edit. Absent when the text has no such reference.
125
+ */
126
+ _display_content: z.string().optional(),
127
+ /** TAL-186: the media `_display_content` references, in content order. */
128
+ _media: z.array(DisplayMediaSchema).optional(),
95
129
  /**
96
130
  * TAL-371: an automatic background wakeup (delegation results, background process or watch notice), not a message the
97
131
  * user sent. Clients render it as a "Background update" disclosure: a localized label per `kind` (with `count`), a
@@ -106,6 +140,13 @@ export const MessageSchema = z.looseObject({
106
140
  */
107
141
  lines: z.array(BackgroundLineSchema),
108
142
  }).optional(),
143
+ /**
144
+ * TAL-305: a marker the Agent wrote around context compaction, not a message anyone typed. Clients render a collapsed
145
+ * card with a localized title per kind and the body on expansion: `_marker_body` when sent, else `content`.
146
+ */
147
+ _marker_kind: z.enum(['context_compaction', 'preserved_task_list']).optional(),
148
+ /** TAL-305: a preserved task list's card body, without its marker line. */
149
+ _marker_body: z.string().optional(),
109
150
  /** TAL-460: part of the Agent's reply to the background update just before it in the same turn. */
110
151
  _background_reply: z.boolean().optional(),
111
152
  /** TAL-460: that reply is only a silence marker; clients show the update's lines and nothing of this turn's reply. */
@@ -142,17 +183,25 @@ const CanDuplicateSchema = z.boolean().describe('Web may duplicate this session:
142
183
  // TAL-310: the server classifies every session's source once; clients file rows by it and never scan source markers.
143
184
  export const SourceKindSchema = z.enum(['webui', 'cli', 'messaging', 'cron', 'webhook', 'subagent', 'claude_code', 'kanban', 'api', 'other'])
144
185
  .describe('The session\'s source family. `is_cli_session` is true only for `cli` and `claude_code`.');
186
+ // TAL-306: clients keep the server's list order and date-bucket by this; they never sort or pick a timestamp themselves.
187
+ const SortTsSchema = z.number().describe('Epoch seconds the session sorts and date-buckets by: `last_message_at`, else `updated_at`, else `created_at`.');
145
188
  const IsMessagingSessionSchema = z.boolean().describe('`source_kind` is `messaging`: a gateway chat (Telegram, Signal, WhatsApp, …) the server imports before Web continues it.');
189
+ /**
190
+ * The label every client shows for the session's workspace: its registered name in the session profile's registry, else
191
+ * the folder name (TAL-303). Null without a workspace; absent from an older server, where clients show no label.
192
+ */
193
+ const WorkspaceNameSchema = NullableString.optional();
146
194
  /** Full session record from `GET /api/session` and mutations returning `session`. */
147
195
  export const SessionSchema = z.looseObject({
148
196
  session_id: SessionIdSchema, title: z.string(), workspace: z.string().optional(), created_workspace: z.string().nullable().optional(), model: NullableString.optional(), model_provider: NullableString.optional(),
197
+ model_option_id: NullableString.optional().describe('TAL-301: the catalog entry id the stored model/provider selects; null when none.'),
149
198
  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
199
  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
200
  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
201
  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
202
  pending_user_message: NullableString.optional(), pending_attachments: z.array(AttachmentSchema).optional(), pending_started_at: NullableNumber.optional(), pending_user_source: NullableString.optional(),
154
203
  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(),
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(),
156
205
  source_label: NullableString.optional(), session_source: NullableString.optional(), raw_source: NullableString.optional(), parent_session_id: NullableString.optional(), worktree_path: NullableString.optional(),
157
206
  worktree_branch: NullableString.optional(), worktree_repo_root: NullableString.optional(), share_token: NullableString.optional(), share_created_at: NullableNumber.optional(), manual_title: z.boolean().optional(),
158
207
  compression_anchor_summary: NullableString.optional(), compression_recovery: z.record(z.string(), Json).optional(), recommended_recovery_action: NullableString.optional(), compression_recovery_action: NullableString.optional(),
@@ -165,19 +214,21 @@ export const SessionSchema = z.looseObject({
165
214
  transcript_seq: z.object({ stream_id: z.string(), seq: z.number().int().nonnegative() }).nullable().optional(),
166
215
  /** The agent's display name: a named profile's own name, else the `bot_name` setting (TAL-458). */
167
216
  assistant_name: z.string().optional(),
217
+ workspace_name: WorkspaceNameSchema,
168
218
  });
169
219
  export const SessionEnvelopeSchema = z.looseObject({ session: SessionSchema });
170
220
  /** Sidebar row from `GET /api/sessions`. */
171
221
  export const SessionRowSchema = z.looseObject({
172
222
  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
223
  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(),
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(),
175
225
  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(),
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(),
227
+ workspace_name: WorkspaceNameSchema,
177
228
  ...ContextUsageFields,
178
229
  });
179
230
  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(),
231
+ 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
232
  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
233
  cli_session_count: z.number().int(),
183
234
  // TAL-482: sidebar counts for the "Scheduled sessions" / "Webhook sessions" groups; `_truncated` means more exist than are listed.
@@ -311,6 +362,8 @@ export const SettingsSchema = z.looseObject({
311
362
  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
363
  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
364
  dictation_append: z.boolean().optional(), persisted_speech_keys: z.array(z.string()).optional(),
365
+ /** TAL-279: ask before opening external chat links; `trusted_link_hosts` are exact, server-normalized hostnames that skip the ask. */
366
+ confirm_external_links: z.boolean().optional(), trusted_link_hosts: z.array(z.string()).optional(),
314
367
  });
315
368
  export const ProfileSchema = z.looseObject({
316
369
  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 +374,58 @@ export const ActiveProfileSchema = z.looseObject({ name: z.string(), path: z.str
321
374
  export const ProfileSwitchSchema = ProfilesSchema.extend({ is_default: z.boolean(), default_model: Json, default_model_provider: Json, default_workspace: z.string().nullable() });
322
375
  /** `/api/reasoning`: config.yaml agent.reasoning_effort / display.show_reasoning, resolved for a model. */
323
376
  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() });
377
+ /** 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. */
378
+ 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
379
  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() });
380
+ 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() });
327
381
  export const ProviderSchema = z.looseObject({
328
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(),
329
383
  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
384
  });
331
385
  export const ProvidersSchema = z.looseObject({ providers: z.array(ProviderSchema), active_provider: NullableString.optional() });
386
+ /** TAL-409: a window's pace as of the envelope's `computed_at`. Stale once `valid_until` (the window's reset) passes. */
387
+ export const QuotaPaceSchema = z.looseObject({
388
+ expected_remaining_percent: z.number(), pace_delta_percent: z.number(), burn_rate: z.number(), minutes_to_reset: z.number(),
389
+ projected_minutes_to_empty: NullableNumber.describe('Minutes until the window empties at the current burn; null when nothing has been used.'),
390
+ elapsed_minutes: z.number(), valid_until: z.string(),
391
+ });
392
+ export const QuotaForecastSchema = z.looseObject({
393
+ outcome: z.enum(['safe', 'warning']).describe('`warning` when the projection empties the window before it resets.'),
394
+ budget_unit: z.enum(['hour', 'day']), budget_percent: NullableNumber.describe('Remaining percent per `budget_unit` until reset.'),
395
+ depletion_margin_minutes: NullableNumber.describe('Projected empty minus reset, in minutes; negative empties early, null when no depletion is projected.'),
396
+ });
397
+ export const QuotaWindowSchema = z.looseObject({
398
+ label: z.string(), used_percent: NullableNumber, remaining_percent: NullableNumber, reset_at: NullableString.describe('ISO-8601 UTC.'), detail: NullableString,
399
+ window_seconds: NullableNumber.describe('The provider value, else 5h or weekly from the label; null when unknown.'),
400
+ pace: QuotaPaceSchema.nullable().describe('Null for a window without a future reset, a usage value, or a 5h/weekly length.'),
401
+ forecast: QuotaForecastSchema.nullable(),
402
+ });
403
+ const QuotaWindowIndex = z.number().int().nullable().optional();
332
404
  export const QuotaSourceSchema = z.looseObject({
333
405
  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(),
406
+ 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
+ pace_window_index: QuotaWindowIndex.describe('The window a pace-coloured widget shows: the weekly one, else the first 5h/session window.'),
408
+ session_window_index: QuotaWindowIndex.describe('The session (else 5h) window.'), weekly_window_index: QuotaWindowIndex.describe('The weekly window.'),
409
+ });
410
+ /** An account-usage provider's normalised snapshot on `/api/provider/quota` (the same windows and indexes as a quotas source). */
411
+ export const QuotaAccountLimitsSchema = z.looseObject({
412
+ available: z.boolean().optional(), stale: z.boolean().optional(), title: z.string().optional(), plan: Json.optional(), unavailable_reason: NullableString.optional(),
413
+ windows: z.array(QuotaWindowSchema), details: Json.optional(), fetched_at: NullableString.describe('ISO-8601 UTC.'),
414
+ pace_window_index: QuotaWindowIndex, session_window_index: QuotaWindowIndex, weekly_window_index: QuotaWindowIndex,
415
+ });
416
+ /** Python `get_provider_quota`: one provider's quota status. */
417
+ export const ProviderQuotaSchema = z.looseObject({
418
+ computed_at: z.string().describe('ISO-8601 UTC reference time of every window `pace`.'),
419
+ ok: z.boolean(), provider: NullableString, display_name: NullableString, supported: z.boolean(), status: z.string(), label: z.string().optional(), message: z.string(),
420
+ quota: Json.describe('OpenRouter credits (`limit_remaining`, `usage`, `limit`), else null.'),
421
+ account_limits: QuotaAccountLimitsSchema.nullable().optional(),
335
422
  });
336
423
  /** 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) });
424
+ export const ProviderQuotasSchema = z.looseObject({
425
+ 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,
426
+ missing_source: z.boolean().describe('True when `?source=` names a source id this scope no longer has; `sources` is then empty.'),
427
+ 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
+ });
338
429
  export const PersonalitiesSchema = z.looseObject({ personalities: z.array(z.looseObject({ name: z.string(), description: z.string().optional() })) });
339
430
  /** One auxiliary task slot in server order (TAL-388). The server owns ordering, normalization, and catalog matching. */
340
431
  export const AuxiliaryTaskSchema = z.looseObject({
@@ -354,7 +445,8 @@ export const AuxiliaryTaskSchema = z.looseObject({
354
445
  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
446
  export const MaxTokensSchema = z.looseObject({ max_tokens: NullableNumber, max_tokens_effective: NullableNumber, max_tokens_fallback: NullableNumber });
356
447
  // ── workspaces, files, git ───────────────────────────────────────────────
357
- export const WorkspaceEntrySchema = z.looseObject({ name: z.string().optional(), path: z.string() });
448
+ /** 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). */
449
+ export const WorkspaceEntrySchema = z.looseObject({ name: z.string().min(1), path: z.string() });
358
450
  export const WorkspacesSchema = z.looseObject({ workspaces: z.array(WorkspaceEntrySchema), last: NullableString.optional(), terminal_remote_backend: z.boolean().optional() });
359
451
  export const FileEntrySchema = z.looseObject({
360
452
  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(),
@@ -375,7 +467,19 @@ export const MemorySchema = z.looseObject({
375
467
  });
376
468
  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
469
  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() });
470
+ /**
471
+ * One slash command in the server's canonical catalog (TAL-314). `GET /api/commands` lists the client-handled commands
472
+ * first, then the Agent registry, in display order; each name appears once. `handler` says who runs it, `clients` which
473
+ * clients can run it, and `unsupported_message` is the English text a client outside `clients` shows when it is typed.
474
+ * 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`.
476
+ */
477
+ export const CommandClientSchema = z.enum(['web', 'ios']);
478
+ export const CommandRowSchema = z.looseObject({
479
+ name: z.string(), description: z.string().optional(), aliases: z.array(z.string()), args_hint: z.string().optional(), category: z.string().optional(),
480
+ 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(),
482
+ });
379
483
  export const CommandsSchema = z.looseObject({ commands: z.array(CommandRowSchema) });
380
484
  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
485
  export const InsightsSchema = z.looseObject({
@@ -393,7 +497,7 @@ export const SystemHealthSchema = z.looseObject({
393
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() });
394
498
  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
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() });
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() });
500
+ 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
501
  export const UpdateNotificationPhaseSchema = z.enum(['applying', 'awaiting_confirmation', 'restarting', 'succeeded', 'blocked', 'failed', 'unknown']);
398
502
  export const UpdateNotificationActionSchema = z.object({
399
503
  id: z.string().min(1).max(64).regex(/^[a-z0-9][a-z0-9_-]*$/),
@@ -424,7 +528,7 @@ export const UpdateNotificationSchema = z.object({
424
528
  acknowledged_action_id: z.string().nullable(),
425
529
  verified_revision: z.string().regex(/^[a-f0-9]{40}$/).nullable(),
426
530
  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. */
531
+ /** 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
532
  detail: z.string().max(2000).nullable(),
429
533
  unread: z.boolean(),
430
534
  active: z.boolean(),
@@ -461,7 +565,8 @@ export const NotesSourcesSchema = z.looseObject({ enabled: z.boolean().optional(
461
565
  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
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() });
463
567
  // ── 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() });
568
+ /** `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
+ 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
570
  export const OnboardingStatusSchema = z.looseObject({
466
571
  completed: z.boolean(),
467
572
  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 +576,25 @@ export const OnboardingStatusSchema = z.looseObject({
471
576
  models: ModelsSchema.optional(),
472
577
  });
473
578
  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() });
579
+ /**
580
+ * One device-code sign-in (TAL-398). `status` is `pending` until the flow ends `approved`, `denied`, `expired`, `cancelled`
581
+ * or `error`; `ok` is false for every ending without a credential, and `error` then carries the Agent's reason.
582
+ * `user_code`, `verification_url`, `expires_in` and `interval` (seconds between polls) come with the start.
583
+ */
584
+ export const OnboardingOAuthSchema = z.looseObject({
585
+ 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(),
586
+ 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(),
587
+ });
475
588
  // ── automation ───────────────────────────────────────────────────────────
476
589
  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
590
  export const CronRepeatSchema = z.looseObject({ times: NullableNumber.optional(), completed: z.number().optional() });
478
591
  export const CronDerivedStateSchema = z.enum(['needs_attention', 'schedule_error', 'paused', 'off', 'error', 'active']);
479
592
  export const CronJobViewSchema = z.looseObject({
480
593
  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(),
594
+ /** Server-filled (TAL-298): `schedule_display` is the schedule text clients show; `schedule_input` is the editor prefill the scheduler accepts back. */
595
+ 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
596
  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(),
597
+ 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
598
  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
599
  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
600
  /** Server-derived (TAL-296): the job's status, whether it needs attention, and whether its primary action is Resume. Live runs overlay it. */