@maudecode/talaria-web-contracts 1.14.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.
Files changed (39) hide show
  1. package/dist/router.d.ts +3594 -317
  2. package/dist/routes/automation.d.ts +123 -7
  3. package/dist/routes/automation.js +3 -1
  4. package/dist/routes/automation.js.map +1 -1
  5. package/dist/routes/chat.d.ts +730 -39
  6. package/dist/routes/chat.js +8 -1
  7. package/dist/routes/chat.js.map +1 -1
  8. package/dist/routes/core.d.ts +12 -6
  9. package/dist/routes/core.js +4 -0
  10. package/dist/routes/core.js.map +1 -1
  11. package/dist/routes/sessions.d.ts +3387 -277
  12. package/dist/routes/sessions.js +30 -6
  13. package/dist/routes/sessions.js.map +1 -1
  14. package/dist/routes/settings.d.ts +188 -17
  15. package/dist/routes/settings.js +7 -6
  16. package/dist/routes/settings.js.map +1 -1
  17. package/dist/routes/tools.d.ts +13 -1
  18. package/dist/routes/workspaces.d.ts +5 -5
  19. package/dist/sidecar/methods.d.ts +221 -6
  20. package/dist/sidecar/methods.js +2 -1
  21. package/dist/sidecar/methods.js.map +1 -1
  22. package/dist/sidecar/namespaces.d.ts +287 -15
  23. package/dist/sidecar/namespaces.js +85 -5
  24. package/dist/sidecar/namespaces.js.map +1 -1
  25. package/dist/sidecar/version.d.ts +1 -1
  26. package/dist/sidecar/version.js +1 -1
  27. package/dist/sse.d.ts +60 -6
  28. package/dist/sse.js +16 -6
  29. package/dist/sse.js.map +1 -1
  30. package/dist/views.d.ts +2036 -176
  31. package/dist/views.js +288 -33
  32. package/dist/views.js.map +1 -1
  33. package/fixtures/sidecar/plugins.json +1 -403
  34. package/fixtures/sidecar/process.json +87 -0
  35. package/fixtures/sidecar/rpc.json +11 -1
  36. package/fixtures/sidecar/runtime.json +3 -3
  37. package/fixtures/sidecar/text.json +0 -10
  38. package/fixtures/sidecar/usage.json +13 -0
  39. package/package.json +1 -1
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() });
@@ -37,11 +44,32 @@ export const MessageContentSchema = z.union([z.string(), z.array(ContentPartSche
37
44
  export const MessageRoleSchema = z.enum(['user', 'assistant', 'system', 'tool']);
38
45
  /** Persisted rows have integer ids; live rows carry string ids. */
39
46
  export const MessageIdSchema = z.union([z.string(), z.number()]);
47
+ /**
48
+ * TAL-372: the background work a delegation tool call started, on its scene row: the session's task ids it links to, their
49
+ * combined status, and the subagent counts the row shows in place ("3 subagents · 2 done, 1 failed").
50
+ */
51
+ export const BackgroundLinkSchema = z.object({
52
+ task_ids: z.array(z.string()),
53
+ status: z.enum(['running', 'attention', 'completed', 'failed', 'cancelled', 'unknown']),
54
+ agents: z.object({ total: z.number().int(), completed: z.number().int(), failed: z.number().int(), running: z.number().int() }),
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']) });
40
64
  /** One normalized activity row: the server decides role, order, tool completion/error, and steering consumption. */
41
65
  export const ActivitySceneRowSchema = z.looseObject({
42
66
  row_id: z.string(), order_index: z.number().int(), role: z.enum(['prose', 'reasoning', 'tool', 'steering']), created_at: z.number().optional(),
43
67
  text: z.string().optional(), titles: z.array(z.string()).optional(),
44
- tool: z.looseObject({ id: z.string(), name: z.string(), ...ToolDisplayFields, args: Json.optional(), preview: z.string().nullable(), result: Json.optional(), done: z.boolean(), is_error: z.boolean(), duration: z.number().nullable(), cost_usd: z.number().nullable() }).optional(),
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(),
45
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(),
46
74
  });
47
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`). */
@@ -50,10 +78,13 @@ export const TurnFileChangeSchema = z.looseObject({ path: z.string(), action: z.
50
78
  /** How a turn ended: every terminal chat frame carries it, and the turn's settled scene shows the same value. */
51
79
  export const TurnTerminalStateSchema = z.enum(['completed', 'no_response', 'cancelled', 'interrupted', 'tool_limit_reached', 'compression_exhausted', 'error']);
52
80
  export const ActivitySceneSchema = z.looseObject({
53
- version: z.literal('activity_scene_v1'), activity_rows: z.array(ActivitySceneRowSchema), final_answer: z.string().optional(), turn_duration: z.number().nullable().optional(),
54
- /** The turn's outcome (`completed`, `no_response`, `error`, `cancelled`, `interrupted`, `tool_limit_reached`, ...). */
55
- terminal_state: z.string().optional(),
56
- /** Whether the "Worked" disclosure opens by default: an unsuccessful outcome with work to read. */
81
+ version: z.literal('activity_scene_v1'), activity_rows: z.array(ActivitySceneRowSchema), final_answer: z.string().optional(),
82
+ /** TAL-456: a settled final answer too long to lay out whole; clients render it collapsed, with a local "Show more". */
83
+ final_answer_excerpt: z.string().optional(), turn_duration: z.number().nullable().optional(),
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. */
57
88
  expanded_by_default: z.boolean().optional(),
58
89
  /** Seconds from the turn's last consumed steer to its end, when it has steers. */
59
90
  final_phase_duration: z.number().optional(),
@@ -63,10 +94,18 @@ export const ActivitySceneSchema = z.looseObject({
63
94
  file_changes: z.array(TurnFileChangeSchema).optional(),
64
95
  activity_rows_total: z.number().int().optional(), activity_rows_offset: z.number().int().optional(), activity_rows_complete: z.boolean().optional(), activity_rows_omitted: z.number().int().optional(), activity_scene_ref: z.string().optional(),
65
96
  });
97
+ /** TAL-460: one finished background item (see `_background_update.lines`). */
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() });
66
99
  export const MessageSchema = z.looseObject({
67
100
  role: z.string(), content: MessageContentSchema.optional(), id: MessageIdSchema.optional(), message_id: MessageIdSchema.optional(), timestamp: z.number().nullable().optional(),
68
- 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(),
69
- 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(),
70
109
  provider_details: Json.optional(), provider_details_label: z.string().optional(), recovery_control: Json.optional(), _anchor_activity_scene: ActivitySceneSchema.optional(),
71
110
  /** The turn this row belongs to; the server stamps every row it sends, so clients group turns by equality alone. */
72
111
  _turn_id: z.string().optional(),
@@ -76,9 +115,64 @@ export const MessageSchema = z.looseObject({
76
115
  _steer: z.looseObject({ steer_id: z.string(), submitted_at: z.number().nullable().optional(), consumed_at: z.number().optional(), phase_duration: z.number().optional() }).optional(),
77
116
  /** On a steered turn's last reply: seconds from the last consumed steer to the turn's end. */
78
117
  _final_phase_duration: z.number().optional(),
118
+ /** A settled body too long to lay out whole: clients render `_display_excerpt` collapsed, with a local "Show more" for `content`. */
119
+ _display_truncated: z.boolean().optional(),
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(),
129
+ /**
130
+ * TAL-371: an automatic background wakeup (delegation results, background process or watch notice), not a message the
131
+ * user sent. Clients render it as a "Background update" disclosure: a localized label per `kind` (with `count`), a
132
+ * warning when `attention`, the server's one-line `summary`, and `content` in full on expansion.
133
+ */
134
+ _background_update: z.object({
135
+ kind: z.enum(['delegation', 'process', 'mixed', 'other']), attention: z.boolean(), count: z.number().int().positive(), summary: z.string(),
136
+ /**
137
+ * TAL-460: one completion line per finished item, shown in place of the row. Clients localize the wording per
138
+ * `kind` and `status`: an agent by its goal (`completed` / `failed`), a command (`finished` / `failed` with
139
+ * `exit_code`), or `label` as written for any other notice. `content` stays available in full on expansion.
140
+ */
141
+ lines: z.array(BackgroundLineSchema),
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(),
150
+ /** TAL-460: part of the Agent's reply to the background update just before it in the same turn. */
151
+ _background_reply: z.boolean().optional(),
152
+ /** TAL-460: that reply is only a silence marker; clients show the update's lines and nothing of this turn's reply. */
153
+ _background_silent: z.boolean().optional(),
79
154
  });
80
155
  export const ComposerDraftSchema = z.looseObject({ text: z.string().optional(), files: z.array(Json).optional() });
156
+ /**
157
+ * TAL-424: a steer the Agent has not taken yet, owned by the server and shown by every client in order. `state`
158
+ * `sending_now`: a Send now is delivering it after the running tools yield. `actions` say what this client may offer.
159
+ */
160
+ export const PendingSteerSchema = z.object({
161
+ steer_id: z.string(), text: z.string(), submitted_at: z.number(), state: z.enum(['pending', 'sending_now']),
162
+ actions: z.object({ edit: z.boolean(), cancel: z.boolean(), send_now: z.boolean() }),
163
+ });
164
+ /**
165
+ * TAL-299: the context ring, computed by the server. `context_used_tokens` is the post-compression estimate, else the last
166
+ * prompt (never the cumulative `input_tokens`); `context_window_tokens` is the model's window. The percents are rounded
167
+ * and capped at 100. Each is null when unknown, and clients then show no percentage.
168
+ */
169
+ export const ContextUsageFields = {
170
+ context_used_tokens: z.number().int().positive().nullable().optional(), context_window_tokens: z.number().int().positive().nullable().optional(),
171
+ context_usage_percent: z.number().int().min(0).max(100).nullable().optional(), context_threshold_percent: z.number().int().min(0).max(100).nullable().optional(),
172
+ };
81
173
  // TAL-312: the server validates both flags on every session payload; clients render them and never re-derive them.
174
+ /** TAL-460: who started the running turn; `background` means a background result did, and the user's next message replaces it. */
175
+ const ActiveTurnOriginSchema = z.enum(['user', 'background']).nullable().optional().describe('Who started the running turn; null while idle.');
82
176
  const IsStreamingSchema = z.boolean().describe('True only while the session\'s run is a live stream on this server.');
83
177
  const ActiveStreamIdSchema = NullableString.optional().describe('The live stream id; non-null only while `is_streaming` is true.');
84
178
  const ReadOnlySchema = z.boolean().describe('The session cannot be modified from Web: a read-only import, a view-only subagent child, or a foreign session whose owner refuses claiming.');
@@ -86,16 +180,28 @@ const CanBranchSchema = z.boolean().describe('Web may branch this session: never
86
180
  const CanPinSchema = z.boolean().describe('Web may pin or unpin this session: never a subagent child.');
87
181
  const CanArchiveSchema = z.boolean().describe('Web may archive or unarchive this session: never a subagent child.');
88
182
  const CanDuplicateSchema = z.boolean().describe('Web may duplicate this session: it has a WebUI copy and is not a subagent child.');
183
+ // TAL-310: the server classifies every session's source once; clients file rows by it and never scan source markers.
184
+ export const SourceKindSchema = z.enum(['webui', 'cli', 'messaging', 'cron', 'webhook', 'subagent', 'claude_code', 'kanban', 'api', 'other'])
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`.');
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();
89
194
  /** Full session record from `GET /api/session` and mutations returning `session`. */
90
195
  export const SessionSchema = z.looseObject({
91
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.'),
92
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(),
93
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(),
94
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(),
95
- cache_hit_percent: NullableNumber.optional(), estimated_cost: NullableNumber.optional(), active_stream_id: ActiveStreamIdSchema, is_streaming: IsStreamingSchema, has_pending_user_message: z.boolean().optional(),
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(),
96
202
  pending_user_message: NullableString.optional(), pending_attachments: z.array(AttachmentSchema).optional(), pending_started_at: NullableNumber.optional(), pending_user_source: NullableString.optional(),
97
- context_length: NullableNumber.optional(), threshold_tokens: NullableNumber.optional(), last_prompt_tokens: NullableNumber.optional(), post_compression_context_tokens_estimate: NullableNumber.optional(),
98
- enabled_toolsets: z.array(z.string()).nullable().optional(), composer_draft: ComposerDraftSchema.optional(), is_cli_session: z.boolean().optional(), read_only: ReadOnlySchema, can_branch: CanBranchSchema, can_pin: CanPinSchema, can_archive: CanArchiveSchema, can_duplicate: CanDuplicateSchema, source_tag: NullableString.optional(),
203
+ 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(),
99
205
  source_label: NullableString.optional(), session_source: NullableString.optional(), raw_source: NullableString.optional(), parent_session_id: NullableString.optional(), worktree_path: NullableString.optional(),
100
206
  worktree_branch: NullableString.optional(), worktree_repo_root: NullableString.optional(), share_token: NullableString.optional(), share_created_at: NullableNumber.optional(), manual_title: z.boolean().optional(),
101
207
  compression_anchor_summary: NullableString.optional(), compression_recovery: z.record(z.string(), Json).optional(), recommended_recovery_action: NullableString.optional(), compression_recovery_action: NullableString.optional(),
@@ -106,23 +212,32 @@ export const SessionSchema = z.looseObject({
106
212
  * `messages` are the whole persisted transcript and a client attaches live without replay.
107
213
  */
108
214
  transcript_seq: z.object({ stream_id: z.string(), seq: z.number().int().nonnegative() }).nullable().optional(),
215
+ /** The agent's display name: a named profile's own name, else the `bot_name` setting (TAL-458). */
216
+ assistant_name: z.string().optional(),
217
+ workspace_name: WorkspaceNameSchema,
109
218
  });
110
219
  export const SessionEnvelopeSchema = z.looseObject({ session: SessionSchema });
111
220
  /** Sidebar row from `GET /api/sessions`. */
112
221
  export const SessionRowSchema = z.looseObject({
113
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(),
114
223
  message_count: z.number().optional(), pinned: z.boolean().optional(), archived: z.boolean().optional(), project_id: NullableString.optional(), profile: NullableString.optional(), is_streaming: IsStreamingSchema,
115
- is_cli_session: z.boolean().optional(), cron_running: z.boolean().optional(), read_only: ReadOnlySchema, can_branch: CanBranchSchema, can_pin: CanPinSchema, can_archive: CanArchiveSchema, can_duplicate: CanDuplicateSchema, attention: z.looseObject({ kind: z.string().optional(), count: z.number().optional() }).nullable().optional(),
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(),
116
225
  source_tag: NullableString.optional(), source_label: NullableString.optional(), session_source: NullableString.optional(), raw_source: NullableString.optional(), parent_session_id: NullableString.optional(),
117
- 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,
228
+ ...ContextUsageFields,
118
229
  });
119
230
  export const SessionsListSchema = z.looseObject({
120
- 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(),
121
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(),
122
- cli_session_count: z.number().int(), archived_limit: z.number().int().nullable().optional(), archived_offset: z.number().int().optional(),
233
+ cli_session_count: z.number().int(),
234
+ // TAL-482: sidebar counts for the "Scheduled sessions" / "Webhook sessions" groups; `_truncated` means more exist than are listed.
235
+ scheduled_session_count: z.number().int().nonnegative(), scheduled_sessions_truncated: z.boolean(),
236
+ webhook_session_count: z.number().int().nonnegative(), webhook_sessions_truncated: z.boolean(),
237
+ archived_limit: z.number().int().nullable().optional(), archived_offset: z.number().int().optional(),
123
238
  });
124
239
  export const SessionStatusSchema = z.looseObject({
125
- session_id: SessionIdSchema, title: z.string().optional(), active_stream_id: ActiveStreamIdSchema, agent_running: IsStreamingSchema, is_streaming: IsStreamingSchema, read_only: ReadOnlySchema, message_count: z.number().optional(), model: NullableString.optional(),
240
+ session_id: SessionIdSchema, title: z.string().optional(), active_stream_id: ActiveStreamIdSchema, active_turn_origin: ActiveTurnOriginSchema, agent_running: IsStreamingSchema, is_streaming: IsStreamingSchema, read_only: ReadOnlySchema, message_count: z.number().optional(), model: NullableString.optional(),
126
241
  profile: NullableString.optional(), workspace: z.string().optional(), input_tokens: z.number().optional(), output_tokens: z.number().optional(), total_tokens: z.number().optional(), estimated_cost: NullableNumber.optional(), updated_at: UnixSeconds.optional(),
127
242
  });
128
243
  export const SessionUsageSchema = z.looseObject({ input_tokens: z.number().optional(), output_tokens: z.number().optional(), total_tokens: z.number().optional(), estimated_cost: NullableNumber.optional(), model: NullableString.optional() });
@@ -140,11 +255,28 @@ export const ChatStartResponseSchema = z.looseObject({
140
255
  title: z.string().optional(), effective_model: z.string().optional(), effective_model_provider: z.string().optional(), queued: z.boolean().optional(), ok: z.boolean().optional(), status: z.string().optional(), reason: z.string().optional(), session: SessionSchema.optional(),
141
256
  });
142
257
  export const StreamStatusSchema = z.looseObject({ active: z.boolean(), stream_id: z.string(), replay_available: z.boolean().optional(), journal: Json.optional() });
143
- export const CancelResponseSchema = z.looseObject({ ok: z.boolean(), cancelled: z.boolean(), stream_id: z.string().optional(), error: z.string().optional() });
258
+ /**
259
+ * TAL-424: a pending steer is no longer pending without the Agent taking it: Edit and Cancel by the user, `stopped` by a
260
+ * Stop (`text` goes back to the composer), or `followup` when the server sends it as the next turn. `steer_id` is null
261
+ * for text another surface queued with the Agent.
262
+ */
263
+ export const SteerWithdrawnSchema = z.object({ steer_id: z.string().nullable(), reason: z.enum(['edit', 'cancel', 'stopped', 'followup']), text: z.string() });
264
+ /**
265
+ * `withdrawn_steers` (TAL-426): the steers this Stop withdrew, the same `steer_withdrawn` (stopped) the stream carries, so
266
+ * a client that stops reading the stream once the Stop answers still gives its own text back.
267
+ */
268
+ export const CancelResponseSchema = z.looseObject({ ok: z.boolean(), cancelled: z.boolean(), stream_id: z.string().optional(), withdrawn_steers: z.array(SteerWithdrawnSchema).optional(), error: z.string().optional() });
269
+ export const SteerWithdrawRequestSchema = z.object({ session_id: SessionIdSchema, steer_id: z.string().min(1), reason: z.enum(['edit', 'cancel']) });
270
+ /** `withdrawn: false` when the steer is unknown, already taken by the Agent (it then settles as consumed), or being sent. */
271
+ export const SteerWithdrawResponseSchema = z.object({ withdrawn: z.boolean(), text: z.string().optional() });
272
+ export const SteerSendNowRequestSchema = z.object({ session_id: SessionIdSchema, steer_id: z.string().min(1) });
273
+ /** `redirected: false` when nothing is live to deliver it to now; the steer stays pending. */
274
+ export const SteerSendNowResponseSchema = z.object({ redirected: z.boolean() });
144
275
  /** `text` is delivered to the running agent; `display_text` is what the transcript shows. */
145
276
  export const SteerRequestSchema = z.looseObject({ session_id: SessionIdSchema, text: z.string().min(1), display_text: z.string().optional(), steer_id: z.string().optional() });
146
277
  /** `accepted: false` with a `fallback` reason means the message was not delivered; the caller keeps the draft. */
147
- export const SteerResponseSchema = z.looseObject({ accepted: z.boolean(), fallback: NullableString.optional(), stream_id: NullableString.optional(), steer_id: z.string().optional() });
278
+ /** TAL-460: a steer sent while a background turn runs starts the user's own turn instead; `started_turn` is its start response. */
279
+ export const SteerResponseSchema = z.looseObject({ accepted: z.boolean(), fallback: NullableString.optional(), stream_id: NullableString.optional(), steer_id: z.string().optional(), started_turn: ChatStartResponseSchema.optional() });
148
280
  export const ApprovalPendingSchema = z.looseObject({
149
281
  approval_id: z.string().optional(), session_id: z.string().optional(), command: z.string().optional(), description: z.string().optional(), title: z.string().optional(), name: z.string().optional(), kind: z.string().optional(),
150
282
  reason: z.string().optional(), action: z.string().optional(), question: z.string().optional(), status: z.string().optional(), pending_count: z.number().optional(), run_id: z.string().optional(), mirror_token: z.string().optional(),
@@ -174,6 +306,42 @@ export const GoalViewSchema = z.looseObject({ text: z.string().optional(), state
174
306
  export const GoalResponseSchema = z.looseObject({ ok: z.boolean().optional(), action: z.string().optional(), goal: GoalViewSchema.nullable().optional(), message: z.string().optional(), message_key: z.string().optional(), stream_id: z.string().optional(), status: z.string().optional(), reason: z.string().optional() });
175
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() });
176
308
  export const BackgroundStatusSchema = z.looseObject({ results: z.array(BackgroundResultSchema) });
309
+ /**
310
+ * TAL-372: one piece of background work a session owns, the same record for every client, reload and restart.
311
+ * - `task_id` is stable: the delegation unit's id, the process id, or the `/background` task id.
312
+ * - `status` `attention` is a stalled agent or a matched watch; `unknown` is running work the Agent cannot confirm now.
313
+ * - `title` is the goal, command or prompt (one line, never output); `agents` counts a delegation's subagents.
314
+ * - `result_available` means `GET /api/background/result` returns its full result.
315
+ * - `pinned` puts it in the chat's background tray: running work, and a finished `/background` result until dismissed.
316
+ * - `dismissible` offers Dismiss: a finished `/background` result, or work nobody can confirm (`unknown`).
317
+ * - `active`: not settled yet (running, attention or unknown); clients keep refreshing while any record is active.
318
+ */
319
+ export const BackgroundTaskSchema = z.object({
320
+ task_id: z.string(),
321
+ kind: z.enum(['delegation', 'process', 'background_command']),
322
+ status: BackgroundLinkSchema.shape.status,
323
+ title: z.string(),
324
+ started_at: z.number().nullable(),
325
+ updated_at: z.number(),
326
+ completed_at: z.number().nullable(),
327
+ result_available: z.boolean(),
328
+ /** TAL-494: each subagent session a delegation ran, by its goal: a read-only transcript to open. */
329
+ child_sessions: z.array(z.object({ goal: z.string(), session_id: SessionIdSchema })),
330
+ exit_code: z.number().int().nullable(),
331
+ agents: BackgroundLinkSchema.shape.agents.nullable(),
332
+ pinned: z.boolean(),
333
+ dismissible: z.boolean(),
334
+ active: z.boolean(),
335
+ });
336
+ /**
337
+ * `agent_available: false` when the Agent could not be asked: running work then shows `unknown`. Reading never consumes a
338
+ * result. `agents_working` (TAL-373): a delegation is running or needs attention, whatever `kind` the list is narrowed to;
339
+ * the chat's side panel opens on Agents for it. Absent from servers before TAL-373.
340
+ */
341
+ export const BackgroundTasksResponseSchema = z.object({ session_id: z.string(), tasks: z.array(BackgroundTaskSchema), agent_available: z.boolean(), agents_working: z.boolean().optional() });
342
+ export const BackgroundTaskResultSchema = z.object({ task_id: z.string(), text: z.string() });
343
+ export const BackgroundDismissRequestSchema = z.object({ session_id: SessionIdSchema, task_id: z.string().min(1) });
344
+ export const BackgroundDismissResponseSchema = z.object({ ok: z.literal(true), task: BackgroundTaskSchema });
177
345
  export const ShareCreateResponseSchema = z.looseObject({ ok: z.literal(true), share: z.looseObject({ token: z.string(), url: z.string(), title: z.string(), message_count: z.number().int(), created_at: z.number(), updated_at: z.number() }), session: SessionRowSchema });
178
346
  export const ShareMessageSchema = z.looseObject({ role: z.string(), content: z.union([z.string(), z.null(), z.array(Json)]).optional() });
179
347
  export const ShareSchema = z.looseObject({ title: z.string(), messages: z.array(ShareMessageSchema), message_count: z.number().int(), created_at: z.number().optional(), updated_at: z.number().optional(), model: NullableString.optional() });
@@ -194,6 +362,8 @@ export const SettingsSchema = z.looseObject({
194
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(),
195
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(),
196
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(),
197
367
  });
198
368
  export const ProfileSchema = z.looseObject({
199
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(),
@@ -204,26 +374,79 @@ export const ActiveProfileSchema = z.looseObject({ name: z.string(), path: z.str
204
374
  export const ProfileSwitchSchema = ProfilesSchema.extend({ is_default: z.boolean(), default_model: Json, default_model_provider: Json, default_workspace: z.string().nullable() });
205
375
  /** `/api/reasoning`: config.yaml agent.reasoning_effort / display.show_reasoning, resolved for a model. */
206
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() });
207
- 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() });
208
379
  export const ModelGroupSchema = z.looseObject({ provider: z.string(), provider_id: z.string().optional(), models: z.array(ModelEntrySchema), extra_models: z.array(ModelEntrySchema).optional() });
209
- 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() });
210
381
  export const ProviderSchema = z.looseObject({
211
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(),
212
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(),
213
384
  });
214
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();
215
404
  export const QuotaSourceSchema = z.looseObject({
216
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(),
217
- 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(),
218
422
  });
219
423
  /** Python `get_provider_quotas`: the stable identity envelope the iOS quota widget persists (`scope_id`/`profile_id`). */
220
- 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
+ });
221
429
  export const PersonalitiesSchema = z.looseObject({ personalities: z.array(z.looseObject({ name: z.string(), description: z.string().optional() })) });
222
- export const AuxiliaryTaskSchema = z.looseObject({ task: z.string(), label: z.string().optional(), description: z.string().optional(), model: z.string().optional(), provider: z.string().optional(), base_url: z.string().optional(), api_key_set: z.boolean().optional() });
223
- export const AuxiliaryModelsSchema = z.looseObject({ main: z.looseObject({ model: z.string().optional(), provider: z.string().optional(), base_url: z.string().optional(), api_key_set: z.boolean().optional() }).optional(), tasks: z.array(AuxiliaryTaskSchema).optional() });
430
+ /** One auxiliary task slot in server order (TAL-388). The server owns ordering, normalization, and catalog matching. */
431
+ export const AuxiliaryTaskSchema = z.looseObject({
432
+ task: z.string(), label: z.string(), description: z.string(),
433
+ /** Saved provider (`auto` when unset) and bare model id. */
434
+ provider: z.string(), model: z.string(),
435
+ base_url: z.string().optional(), api_key_set: z.boolean().optional(),
436
+ /** No override is saved: the task falls back to the main chat model. */
437
+ is_auto: z.boolean(),
438
+ /** Display of the model the task uses (the main chat model when `is_auto`): catalog label, else the saved model id, and its provider name. */
439
+ value_label: z.string().nullable(), provider_label: z.string().nullable(),
440
+ /** The `/api/models` entry id matching the saved provider/model pair; null for Auto or a model absent from the catalog. */
441
+ selected_option_id: z.string().nullable(),
442
+ /** False only for a pinned model the current catalog does not list; it stays visible and editable. */
443
+ in_catalog: z.boolean(),
444
+ });
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) });
224
446
  export const MaxTokensSchema = z.looseObject({ max_tokens: NullableNumber, max_tokens_effective: NullableNumber, max_tokens_fallback: NullableNumber });
225
447
  // ── workspaces, files, git ───────────────────────────────────────────────
226
- 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() });
227
450
  export const WorkspacesSchema = z.looseObject({ workspaces: z.array(WorkspaceEntrySchema), last: NullableString.optional(), terminal_remote_backend: z.boolean().optional() });
228
451
  export const FileEntrySchema = z.looseObject({
229
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(),
@@ -244,7 +467,19 @@ export const MemorySchema = z.looseObject({
244
467
  });
245
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() });
246
469
  export const PromptsSchema = z.looseObject({ prompts: z.array(PromptSchema) });
247
- 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
+ });
248
483
  export const CommandsSchema = z.looseObject({ commands: z.array(CommandRowSchema) });
249
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() });
250
485
  export const InsightsSchema = z.looseObject({
@@ -262,7 +497,7 @@ export const SystemHealthSchema = z.looseObject({
262
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() });
263
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() });
264
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() });
265
- 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() });
266
501
  export const UpdateNotificationPhaseSchema = z.enum(['applying', 'awaiting_confirmation', 'restarting', 'succeeded', 'blocked', 'failed', 'unknown']);
267
502
  export const UpdateNotificationActionSchema = z.object({
268
503
  id: z.string().min(1).max(64).regex(/^[a-z0-9][a-z0-9_-]*$/),
@@ -293,7 +528,7 @@ export const UpdateNotificationSchema = z.object({
293
528
  acknowledged_action_id: z.string().nullable(),
294
529
  verified_revision: z.string().regex(/^[a-f0-9]{40}$/).nullable(),
295
530
  verified_version: z.string().min(1).max(80).nullable(),
296
- /** 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. */
297
532
  detail: z.string().max(2000).nullable(),
298
533
  unread: z.boolean(),
299
534
  active: z.boolean(),
@@ -330,7 +565,8 @@ export const NotesSourcesSchema = z.looseObject({ enabled: z.boolean().optional(
330
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() });
331
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() });
332
567
  // ── onboarding ───────────────────────────────────────────────────────────
333
- 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() });
334
570
  export const OnboardingStatusSchema = z.looseObject({
335
571
  completed: z.boolean(),
336
572
  settings: z.looseObject({ bot_name: z.string().optional(), default_model: z.string().optional(), default_workspace: NullableString.optional(), password_enabled: z.boolean().optional() }).optional(),
@@ -340,19 +576,38 @@ export const OnboardingStatusSchema = z.looseObject({
340
576
  models: ModelsSchema.optional(),
341
577
  });
342
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() });
343
- 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
+ });
344
588
  // ── automation ───────────────────────────────────────────────────────────
345
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() });
346
590
  export const CronRepeatSchema = z.looseObject({ times: NullableNumber.optional(), completed: z.number().optional() });
591
+ export const CronDerivedStateSchema = z.enum(['needs_attention', 'schedule_error', 'paused', 'off', 'error', 'active']);
347
592
  export const CronJobViewSchema = z.looseObject({
348
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(),
349
- 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(),
350
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(),
351
- 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(),
352
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(),
353
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(),
600
+ /** Server-derived (TAL-296): the job's status, whether it needs attention, and whether its primary action is Resume. Live runs overlay it. */
601
+ derived_state: CronDerivedStateSchema.optional(), needs_attention: z.boolean().optional(), resumable: z.boolean().optional(),
354
602
  });
355
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
+ export const CronContextSourcesSchema = z.object({ profile: z.string(), sources: z.array(z.object({ job_id: z.string(), label: z.string(), selectable: z.boolean() })) });
605
+ /** `GET /api/crons/recent` row: one job's latest completion, ordered and classified by the server. */
606
+ export const CronRecentCompletionSchema = z.object({
607
+ job_id: z.string(), name: z.string().nullable(), status: z.string().nullable(), outcome: z.enum(['succeeded', 'failed', 'unknown']), completed_at: z.number(),
608
+ toast_notifications: z.boolean(), session_id: z.string(), message_count: z.number().int().optional(),
609
+ });
610
+ export const CronRecentSchema = z.object({ completions: z.array(CronRecentCompletionSchema), since: z.number() });
356
611
  export const CronMutationSchema = z.looseObject({ ok: z.boolean().optional(), job: CronJobViewSchema.optional(), job_id: z.string().optional(), status: z.string().optional(), error: z.string().optional(), elapsed: z.number().optional() });
357
612
  export const CronRunUsageSchema = z.looseObject({ input_tokens: NullableNumber.optional(), output_tokens: NullableNumber.optional(), total_tokens: NullableNumber.optional(), estimated_cost_usd: NullableNumber.optional(), duration_seconds: NullableNumber.optional(), model: z.string().optional(), provider: z.string().optional() });
358
613
  export const CronRunSummarySchema = z.looseObject({ filename: z.string(), size: z.number(), modified: z.number(), usage: CronRunUsageSchema.optional() });