@maudecode/talaria-web-contracts 1.18.0 → 1.19.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (82) hide show
  1. package/dist/gateway-session.d.ts +229 -0
  2. package/dist/gateway-session.js +46 -0
  3. package/dist/gateway-session.js.map +1 -0
  4. package/dist/index.d.ts +3 -0
  5. package/dist/index.js +3 -0
  6. package/dist/index.js.map +1 -1
  7. package/dist/openapi.js +4 -0
  8. package/dist/openapi.js.map +1 -1
  9. package/dist/router.d.ts +4265 -220
  10. package/dist/routes/automation.d.ts +104 -24
  11. package/dist/routes/automation.js +6 -3
  12. package/dist/routes/automation.js.map +1 -1
  13. package/dist/routes/chat.d.ts +1338 -43
  14. package/dist/routes/chat.js +5 -1
  15. package/dist/routes/chat.js.map +1 -1
  16. package/dist/routes/core.d.ts +17 -6
  17. package/dist/routes/core.js +2 -2
  18. package/dist/routes/core.js.map +1 -1
  19. package/dist/routes/raw.js +1 -1
  20. package/dist/routes/raw.js.map +1 -1
  21. package/dist/routes/sessions.d.ts +2367 -156
  22. package/dist/routes/sessions.js +18 -7
  23. package/dist/routes/sessions.js.map +1 -1
  24. package/dist/routes/settings.d.ts +427 -9
  25. package/dist/routes/settings.js +2 -2
  26. package/dist/routes/settings.js.map +1 -1
  27. package/dist/routes/tools.d.ts +645 -18
  28. package/dist/routes/tools.js +65 -8
  29. package/dist/routes/tools.js.map +1 -1
  30. package/dist/run-status.d.ts +39 -0
  31. package/dist/run-status.js +13 -0
  32. package/dist/run-status.js.map +1 -0
  33. package/dist/sidecar/methods.d.ts +1074 -17
  34. package/dist/sidecar/methods.js +9 -2
  35. package/dist/sidecar/methods.js.map +1 -1
  36. package/dist/sidecar/namespaces.d.ts +1469 -33
  37. package/dist/sidecar/namespaces.js +298 -31
  38. package/dist/sidecar/namespaces.js.map +1 -1
  39. package/dist/sidecar/runtime.d.ts +61 -0
  40. package/dist/sidecar/runtime.js +20 -0
  41. package/dist/sidecar/runtime.js.map +1 -1
  42. package/dist/sidecar/version.d.ts +1 -1
  43. package/dist/sidecar/version.js +1 -1
  44. package/dist/speed.d.ts +14 -0
  45. package/dist/speed.js +6 -0
  46. package/dist/speed.js.map +1 -0
  47. package/dist/sse.d.ts +797 -5
  48. package/dist/sse.js +24 -7
  49. package/dist/sse.js.map +1 -1
  50. package/dist/transcribe-stream.d.ts +34 -0
  51. package/dist/transcribe-stream.js +23 -0
  52. package/dist/transcribe-stream.js.map +1 -0
  53. package/dist/views.d.ts +2097 -127
  54. package/dist/views.js +273 -47
  55. package/dist/views.js.map +1 -1
  56. package/fixtures/sidecar/aux.json +134 -0
  57. package/fixtures/sidecar/chat.json +14 -0
  58. package/fixtures/sidecar/checkpoints.json +6 -6
  59. package/fixtures/sidecar/commands.json +216 -108
  60. package/fixtures/sidecar/config.json +15 -0
  61. package/fixtures/sidecar/connectors.json +218 -0
  62. package/fixtures/sidecar/cron.json +1001 -31
  63. package/fixtures/sidecar/gateway.json +39 -0
  64. package/fixtures/sidecar/goals.json +5 -3
  65. package/fixtures/sidecar/kanban.json +139 -49
  66. package/fixtures/sidecar/memory.json +50 -0
  67. package/fixtures/sidecar/models.json +122 -0
  68. package/fixtures/sidecar/personalities.json +92 -0
  69. package/fixtures/sidecar/plugins.json +116 -27
  70. package/fixtures/sidecar/process.json +31 -0
  71. package/fixtures/sidecar/profiles.json +2 -0
  72. package/fixtures/sidecar/providers.json +14 -1
  73. package/fixtures/sidecar/rpc.json +35 -1
  74. package/fixtures/sidecar/runtime.json +130 -14
  75. package/fixtures/sidecar/skills.json +256 -13
  76. package/fixtures/sidecar/state_db.json +19 -2
  77. package/fixtures/sidecar/stt.json +62 -1
  78. package/fixtures/sidecar/text.json +20 -2
  79. package/fixtures/sidecar/toolsets.json +468 -0
  80. package/fixtures/sidecar/usage.json +22 -2
  81. package/fixtures/sidecar/write_approval.json +98 -0
  82. package/package.json +1 -1
package/dist/views.js CHANGED
@@ -5,7 +5,12 @@
5
5
  * that must survive a round trip (documented passthrough).
6
6
  */
7
7
  import { z } from 'zod';
8
- import { CanonicalBotChatSchema } from './sidecar/namespaces.js';
8
+ import { RunStatusSchema } from './run-status.js';
9
+ import { AgentRefreshConfirmationSchema } from './sidecar/runtime.js';
10
+ import { SpeedTierSchema } from './speed.js';
11
+ import { OwnerAttachmentSchema } from './gateway-session.js';
12
+ import { CanonicalBotChatSchema, GatewayModeSchema, PluginSettingFieldSchema, SkillCatalogIdentitySchema } from './sidecar/namespaces.js';
13
+ export { SpeedTierSchema } from './speed.js';
9
14
  const Json = z.unknown();
10
15
  export const NullableString = z.string().nullable();
11
16
  export const NullableNumber = z.number().nullable();
@@ -100,8 +105,8 @@ export const TurnFileChangeSchema = z.looseObject({ path: z.string(), action: z.
100
105
  export const TurnTerminalStateSchema = z.enum(['completed', 'no_response', 'cancelled', 'interrupted', 'tool_limit_reached', 'compression_exhausted', 'error']);
101
106
  export const ActivitySceneSchema = z.looseObject({
102
107
  version: z.literal('activity_scene_v1'), activity_rows: z.array(ActivitySceneRowSchema), final_answer: z.string().optional(),
103
- /** TAL-456: a settled final answer too long to lay out whole; clients render it collapsed, with a local "Show more". */
104
- final_answer_excerpt: z.string().optional(), turn_duration: z.number().nullable().optional(),
108
+ /** TAL-456: an older settled final answer too long to lay out whole. Absent on the session-global newest settled visible reply and throughout the next running turn. Clients render the excerpt with a local "Show more". */
109
+ final_answer_excerpt: z.string().optional().describe("Collapsed preview of an older settled final answer. Absent on the session-global newest visible reply; its exemption transfers only when a newer visible reply settles."), turn_duration: z.number().nullable().optional(),
105
110
  /** TAL-186: `final_answer` with its media references rewritten for display (see `_display_content`), and its media. */
106
111
  final_answer_display: z.string().optional(), final_answer_media: z.array(DisplayMediaSchema).optional(),
107
112
  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.'),
@@ -115,8 +120,13 @@ export const ActivitySceneSchema = z.looseObject({
115
120
  file_changes: z.array(TurnFileChangeSchema).optional(),
116
121
  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(),
117
122
  });
118
- /** TAL-460: one finished background item (see `_background_update.lines`). */
119
- 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() });
123
+ /** The server-owned result-review lifecycle (TAL-1022). */
124
+ export const BackgroundReviewStateSchema = z.enum(['waiting', 'reviewing', 'reviewed', 'failed', 'unknown']);
125
+ /** One finished background item (see `_background_update.lines`). */
126
+ 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(),
127
+ /** Server-composed worker and parent outcome. Failure/unknown worker outcomes take precedence. */
128
+ review_state: BackgroundReviewStateSchema.optional(),
129
+ });
120
130
  export const MessageSchema = z.looseObject({
121
131
  role: z.string(), content: MessageContentSchema.optional(), id: MessageIdSchema.optional(), message_id: MessageIdSchema.optional(), timestamp: z.number().nullable().optional(),
122
132
  attachments: z.array(AttachmentSchema).optional(), tool_calls: z.array(ToolCallSchema).optional(),
@@ -144,9 +154,9 @@ export const MessageSchema = z.looseObject({
144
154
  _steer: z.looseObject({ steer_id: z.string(), submitted_at: z.number().nullable().optional(), consumed_at: z.number().optional(), phase_duration: z.number().optional() }).optional(),
145
155
  /** On a steered turn's last reply: seconds from the last consumed steer to the turn's end. */
146
156
  _final_phase_duration: z.number().optional(),
147
- /** A settled body too long to lay out whole: clients render `_display_excerpt` collapsed, with a local "Show more" for `content`. */
148
- _display_truncated: z.boolean().optional(),
149
- _display_excerpt: z.string().optional(),
157
+ /** A settled body too long to lay out whole. The session-global newest settled visible assistant reply and running output carry no excerpt. Only a newer visible terminal answer transfers that exemption. Clients render `_display_excerpt` with a local "Show more" for full `content`. */
158
+ _display_truncated: z.boolean().optional().describe("Whether this settled body has a collapsed preview. The newest settled visible assistant reply and active-turn output remain full."),
159
+ _display_excerpt: z.string().optional().describe("Collapsed preview with full content retained. The newest settled visible assistant reply remains exempt throughout the next running turn, until a newer visible reply settles."),
150
160
  /**
151
161
  * TAL-452: a typed user message (never a steer, background update or marker) longer than 20 lines or 2,000
152
162
  * characters. Clients show it folded to a few lines with a local "Show more"; copy and edit use the whole `content`.
@@ -168,11 +178,13 @@ export const MessageSchema = z.looseObject({
168
178
  _background_update: z.object({
169
179
  kind: z.enum(['delegation', 'process', 'mixed', 'other']), attention: z.boolean(), count: z.number().int().positive(), summary: z.string(),
170
180
  /**
171
- * TAL-460: one completion line per finished item, shown in place of the row. Clients localize the wording per
172
- * `kind` and `status`: an agent by its goal (`completed` / `failed`), a command (`finished` / `failed` with
173
- * `exit_code`), or `label` as written for any other notice. `content` stays available in full on expansion.
181
+ * One completion line per finished item. Clients show its label and exactly one leading icon from `review_state`,
182
+ * with localized accessible/help wording. Parent review never overrides failed/unknown worker outcomes.
183
+ * `content` stays available in full on expansion.
174
184
  */
175
185
  lines: z.array(BackgroundLineSchema),
186
+ /** Parent review only, established from admission, the live run, and terminal evidence. */
187
+ review_state: BackgroundReviewStateSchema.optional(),
176
188
  }).optional(),
177
189
  /**
178
190
  * TAL-305: a marker the Agent wrote around context compaction, not a message anyone typed. Clients render a collapsed
@@ -181,6 +193,19 @@ export const MessageSchema = z.looseObject({
181
193
  _marker_kind: z.enum(['context_compaction', 'preserved_task_list']).optional(),
182
194
  /** TAL-305: a preserved task list's card body, without its marker line. */
183
195
  _marker_body: z.string().optional(),
196
+ /**
197
+ * TAL-815: an assistant row the Agent wrote as a notice, not the model's reply. `failed_turn`: the boundary closing a
198
+ * turn that failed, with its copy in `content`. Clients show it as a notice in its place, never as a reply bubble or
199
+ * part of a turn's reply; it is absent when Web's own error row already reports that turn. `session_finalize` is
200
+ * a plugin's closing text after clear, with role `local_notice`, display-only and excluded from model history.
201
+ */
202
+ _notice_kind: z.enum(['failed_turn', 'session_finalize']).optional(),
203
+ /**
204
+ * TAL-816: a user row of a messaging session that the Agent's gateway mirrored after a webhook route's or cron job's
205
+ * delivery to the chat, not a message the user sent. Clients show it as a delivery notice titled by a localized label
206
+ * per `kind` with `name`, and `body` (the delivered text, without its label line) beneath, never as a user bubble.
207
+ */
208
+ _delivery: z.object({ kind: z.enum(['webhook', 'cron']), name: z.string(), body: z.string() }).optional(),
184
209
  /** TAL-460: part of the Agent's reply to the background update just before it in the same turn. */
185
210
  _background_reply: z.boolean().optional(),
186
211
  /** TAL-460: that reply is only a silence marker; clients show the update's lines and nothing of this turn's reply. */
@@ -201,6 +226,23 @@ export const PendingSteerSchema = z.object({
201
226
  steer_id: z.string(), text: z.string(), submitted_at: z.number(), state: z.enum(['pending', 'sending_now']),
202
227
  actions: z.object({ edit: z.boolean(), cancel: z.boolean(), send_now: z.boolean() }),
203
228
  });
229
+ /**
230
+ * TAL-803: an out-of-band notice the Agent raised for the session (a memory provider that failed to load, credit and
231
+ * usage warnings), one per `key`. The server keeps it until the Agent clears that key, `expires_at` passes, or a new agent
232
+ * replaces the one that raised it.
233
+ */
234
+ export const SessionNoticeSchema = z.object({
235
+ key: z.string(), level: z.enum(['info', 'warn', 'error', 'success']), text: z.string(),
236
+ expires_at: NullableNumber.describe('Unix seconds when the server drops it; null when only the Agent clears it.'),
237
+ });
238
+ /**
239
+ * TAL-808: the session's standing `/goal` while it runs, from the Agent's goal state. `parked` is an active goal waiting on
240
+ * a process or a timer; `reason` is why it is paused or what it waits on. A done, cleared or absent goal is null.
241
+ */
242
+ export const SessionGoalSchema = z.object({
243
+ text: z.string(), status: z.enum(['active', 'paused', 'parked']), turns_used: z.number().int().nonnegative(), max_turns: z.number().int().nonnegative(),
244
+ reason: NullableString, parked_until: NullableNumber.describe('Unix seconds a timed park ends; null otherwise.'),
245
+ });
204
246
  /**
205
247
  * TAL-299: the context ring, computed by the server. `context_used_tokens` is the post-compression estimate, else the last
206
248
  * prompt (never the cumulative `input_tokens`); `context_window_tokens` is the model's window. The percents are rounded
@@ -215,12 +257,13 @@ export const ContextUsageFields = {
215
257
  const ActiveTurnOriginSchema = z.enum(['user', 'background']).nullable().optional().describe('Who started the running turn; null while idle.');
216
258
  const IsStreamingSchema = z.boolean().describe('True only while the session\'s run is a live stream on this server.');
217
259
  const ActiveStreamIdSchema = NullableString.optional().describe('The live stream id; non-null only while `is_streaming` is true.');
260
+ const ReadOnlyReasonSchema = NullableString.optional().describe('Server-owned reason this session is display-only or its ownership cannot be verified.');
218
261
  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.');
219
- const CanBranchSchema = z.boolean().describe('Web may branch this session: never a subagent child, and a read-only session only when it is a cron run.');
220
- const CanPinSchema = z.boolean().describe('Web may pin or unpin this session: never a subagent child.');
221
- const CanArchiveSchema = z.boolean().describe('Web may archive or unarchive this session: never a subagent child.');
262
+ const CanBranchSchema = z.boolean().describe('Web may branch this session: never a subagent child or an ownership-locked session; other read-only sessions only when they are cron runs.');
263
+ const CanPinSchema = z.boolean().describe('Web may pin or unpin this session: never a subagent child or a session locked by canonical ownership or unreadable ownership storage.');
264
+ const CanArchiveSchema = z.boolean().describe('Web may archive or unarchive this session: never a subagent child or a session locked by canonical ownership or unreadable ownership storage.');
222
265
  const CanDeleteSchema = z.boolean().describe('Web may delete this session: never a read-only session (a read-only import, a view-only subagent child, or a foreign session whose owner refuses claiming).');
223
- const CanDuplicateSchema = z.boolean().describe('Web may duplicate this session: it has a WebUI copy and is not a subagent child.');
266
+ const CanDuplicateSchema = z.boolean().describe('Web may duplicate this session: it has a WebUI copy, is not a subagent child, and is not locked by canonical ownership or unreadable ownership storage.');
224
267
  // TAL-310: the server classifies every session's source once; clients file rows by it and never scan source markers.
225
268
  export const SourceKindSchema = z.enum(['webui', 'cli', 'messaging', 'cron', 'webhook', 'subagent', 'claude_code', 'kanban', 'api', 'other'])
226
269
  .describe('The session\'s source family. `is_cli_session` is true only for `cli` and `claude_code`.');
@@ -245,16 +288,23 @@ export const CompressionReferenceSchema = z.object({
245
288
  export const BranchedFromSchema = z.object({ session_id: SessionIdSchema, title: z.string() });
246
289
  /** Full session record from `GET /api/session` and mutations returning `session`. */
247
290
  export const SessionSchema = z.looseObject({
291
+ composer_state_key: z.string().optional().describe('Opaque profile-home and owner identity for device-local composer state; never an API session id.'),
248
292
  session_id: SessionIdSchema, title: z.string(), workspace: z.string().optional(), created_workspace: z.string().nullable().optional(), model: NullableString.optional(), model_provider: NullableString.optional(),
249
293
  model_option_id: NullableString.optional().describe('TAL-301: the catalog entry id the stored model/provider selects; null when none.'),
250
294
  messages: z.array(MessageSchema).optional(), tool_calls: z.array(ToolCallSchema).optional(), created_at: UnixSeconds.optional(), updated_at: UnixSeconds.optional(), last_message_at: NullableNumber.optional(),
251
295
  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(),
252
296
  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(),
253
- cache_hit_percent: NullableNumber.optional(), estimated_cost: NullableNumber.optional(), active_stream_id: ActiveStreamIdSchema, is_streaming: IsStreamingSchema, active_turn_origin: ActiveTurnOriginSchema, pending_steers: z.array(PendingSteerSchema).optional().describe('TAL-424: the active stream\'s pending steers, oldest first.'), has_pending_user_message: z.boolean().optional(),
297
+ 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.'),
298
+ run_status: RunStatusSchema.nullable().optional().describe('Current server-selected run presentation; null when no turn runs. Older servers omit it.'),
299
+ compression_started_at: NullableNumber.optional().describe('TAL-1006: when the compression clients show started (epoch seconds): the running manual job\'s, else the live run\'s open span; null when none runs.'),
300
+ notices: z.array(SessionNoticeSchema).optional().describe('TAL-803: the Agent\'s active notices for the session, oldest first.'),
301
+ goal: SessionGoalSchema.nullable().optional().describe('TAL-808: the standing /goal; null when none runs.'),
302
+ automatic_turns_held: z.boolean().optional().describe('TAL-899: the user pressed Stop, so no automatic turn (background wakeup or goal continuation) starts until their next message.'), has_pending_user_message: z.boolean().optional(),
254
303
  pending_user_message: NullableString.optional(), pending_attachments: z.array(AttachmentSchema).optional(), pending_started_at: NullableNumber.optional(), pending_user_source: NullableString.optional(),
255
304
  context_length: NullableNumber.optional(), threshold_tokens: NullableNumber.optional(), last_prompt_tokens: NullableNumber.optional(), post_compression_context_tokens_estimate: NullableNumber.optional(), ...ContextUsageFields,
256
- enabled_toolsets: z.array(z.string()).nullable().optional(), composer_draft: ComposerDraftSchema.optional(), is_cli_session: z.boolean().optional(), source_kind: SourceKindSchema, is_messaging_session: IsMessagingSessionSchema, sort_ts: SortTsSchema, read_only: ReadOnlySchema, can_branch: CanBranchSchema, can_pin: CanPinSchema, can_archive: CanArchiveSchema, can_delete: CanDeleteSchema, can_duplicate: CanDuplicateSchema, source_tag: NullableString.optional(),
305
+ 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, read_only_reason: ReadOnlyReasonSchema, owner_attachment: OwnerAttachmentSchema.optional(), can_branch: CanBranchSchema, can_pin: CanPinSchema, can_archive: CanArchiveSchema, can_delete: CanDeleteSchema, can_duplicate: CanDuplicateSchema, source_tag: NullableString.optional(),
257
306
  can_compress: z.boolean().optional().describe('TAL-614: a manual compression would start now: writable, no running turn, and at least four model-history messages. Only the full session detail carries it; absent means no.'),
307
+ confirms_deep_truncate: z.boolean().optional().describe('TAL-929: this server holds a `session.truncate` that discards more than one user turn until it is confirmed; absent on older servers, which cut at once.'),
258
308
  source_label: NullableString.optional(), session_source: NullableString.optional(), raw_source: NullableString.optional(), parent_session_id: NullableString.optional(), worktree_path: NullableString.optional(),
259
309
  worktree_branch: NullableString.optional(), worktree_repo_root: NullableString.optional(), share_token: NullableString.optional(), share_url: NullableString.optional(), share_created_at: NullableNumber.optional(), manual_title: z.boolean().optional(),
260
310
  compression_anchor_summary: NullableString.optional(), compression_recovery: z.record(z.string(), Json).optional(), recommended_recovery_action: NullableString.optional(), compression_recovery_action: NullableString.optional(),
@@ -276,12 +326,18 @@ export const SessionEnvelopeSchema = z.looseObject({ session: SessionSchema });
276
326
  export const SessionRowSchema = z.looseObject({
277
327
  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(),
278
328
  message_count: z.number().optional(), pinned: z.boolean().optional(), archived: z.boolean().optional(), project_id: NullableString.optional(), profile: NullableString.optional(), is_streaming: IsStreamingSchema,
279
- is_cli_session: z.boolean().optional(), source_kind: SourceKindSchema, is_messaging_session: IsMessagingSessionSchema, sort_ts: SortTsSchema, cron_running: z.boolean().optional(), read_only: ReadOnlySchema, can_branch: CanBranchSchema, can_pin: CanPinSchema, can_archive: CanArchiveSchema, can_delete: CanDeleteSchema, can_duplicate: CanDuplicateSchema, attention: z.looseObject({ kind: z.string().optional(), count: z.number().optional() }).nullable().optional(),
329
+ is_cli_session: z.boolean().optional(), source_kind: SourceKindSchema, is_messaging_session: IsMessagingSessionSchema, sort_ts: SortTsSchema, cron_running: z.boolean().optional(), read_only: ReadOnlySchema, read_only_reason: ReadOnlyReasonSchema, owner_attachment: OwnerAttachmentSchema.optional(), can_branch: CanBranchSchema, can_pin: CanPinSchema, can_archive: CanArchiveSchema, can_delete: CanDeleteSchema, can_duplicate: CanDuplicateSchema, attention: z.looseObject({ kind: z.string().optional(), count: z.number().optional() }).nullable().optional(),
280
330
  source_tag: NullableString.optional(), source_label: NullableString.optional(), session_source: NullableString.optional(), raw_source: NullableString.optional(), parent_session_id: NullableString.optional(),
281
331
  active_stream_id: ActiveStreamIdSchema, share_token: NullableString.optional(), share_url: NullableString.optional(), worktree_branch: NullableString.optional(), match_type: z.enum(['title', 'metadata', 'content']).optional(), match_preview: NullableString.optional(),
282
332
  workspace_name: WorkspaceNameSchema,
283
333
  ...ContextUsageFields,
284
334
  });
335
+ /**
336
+ * TAL-785: whether the Agent's `state.db` could serve the Agent session rows. `corrupt`: SQLite reports the file
337
+ * damaged; `unavailable`: it could not be read for another reason (permissions, a lock, an I/O error); `outdated`: a
338
+ * readable database from an older hermes-agent. `reason` explains a degraded state.
339
+ */
340
+ export const AgentStorageSchema = z.object({ state: z.enum(['ok', 'corrupt', 'unavailable', 'outdated']), reason: z.string().nullable() });
285
341
  export const SessionsListSchema = z.looseObject({
286
342
  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(),
287
343
  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(),
@@ -289,10 +345,11 @@ export const SessionsListSchema = z.looseObject({
289
345
  // TAL-482: sidebar counts for the "Scheduled sessions" / "Webhook sessions" groups; `_truncated` means more exist than are listed.
290
346
  scheduled_session_count: z.number().int().nonnegative(), scheduled_sessions_truncated: z.boolean(),
291
347
  webhook_session_count: z.number().int().nonnegative(), webhook_sessions_truncated: z.boolean(),
348
+ agent_storage: AgentStorageSchema.describe('Agent session storage health; a degraded state explains an empty or missing Agent session list.'),
292
349
  archived_limit: z.number().int().nullable().optional(), archived_offset: z.number().int().optional(),
293
350
  });
294
351
  export const SessionStatusSchema = z.looseObject({
295
- 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(),
352
+ session_id: SessionIdSchema, title: z.string().optional(), active_stream_id: ActiveStreamIdSchema, active_turn_origin: ActiveTurnOriginSchema, agent_running: IsStreamingSchema, is_streaming: IsStreamingSchema, read_only: ReadOnlySchema, read_only_reason: ReadOnlyReasonSchema, owner_attachment: OwnerAttachmentSchema.optional(), message_count: z.number().optional(), model: NullableString.optional(),
296
353
  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(),
297
354
  });
298
355
  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() });
@@ -300,12 +357,20 @@ export const ProjectSchema = z.looseObject({ project_id: z.string(), name: z.str
300
357
  export const ProjectsSchema = z.looseObject({ projects: z.array(ProjectSchema), active_profile: z.string(), all_profiles: z.boolean(), other_profile_count: z.number().int() });
301
358
  export const SessionDeleteResultSchema = z.looseObject({ ok: z.literal(true), state_db_cleanup_failed: z.boolean().optional() });
302
359
  // ── chat ─────────────────────────────────────────────────────────────────
360
+ /** `voice_turn` (TAL-801): the turn was spoken (an App voice note), so the Agent runs it on `auxiliary.voice_chat`. */
303
361
  export const ChatStartRequestSchema = z.looseObject({
362
+ submission_id: z.string().min(1).max(128).optional().describe('Stable native-owner admission identity. Reuse only for an exact retry of the same submission.'),
304
363
  session_id: SessionIdSchema, message: z.string().optional(), model: z.string().nullable().optional(), model_provider: z.string().nullable().optional(), workspace: z.string().nullable().optional(), profile: z.string().nullable().optional(),
305
364
  explicit_model_pick: z.boolean().optional(), attachments: z.array(z.union([AttachmentSchema, z.string()])).optional(), moa_config: z.boolean().optional(), regenerate: z.boolean().optional(), regeneration_revision: z.string().optional(),
365
+ voice_turn: z.boolean().optional(),
366
+ /** TAL-818: this client renders `connect_card`, so a `manage_connections` call waits on the card; without it the Agent answers with plain links. */
367
+ connect_card: z.boolean().optional(),
368
+ /** The starting client renders the masked `skill_secret` prompt; omitted by older and native clients. */
369
+ skill_secret: z.boolean().optional(),
306
370
  });
307
371
  /** Accepted turn. `stream_id` is the owner identity for the SSE connection. */
308
372
  export const ChatStartResponseSchema = z.looseObject({
373
+ owner_attachment: OwnerAttachmentSchema.optional(), admission_id: z.string().optional(),
309
374
  stream_id: z.string().optional(), session_id: SessionIdSchema.optional(), turn_id: z.string().optional(), user_message_id: z.union([z.string(), z.number()]).nullable().optional(), pending_started_at: z.number().nullable().optional(),
310
375
  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(),
311
376
  });
@@ -330,14 +395,15 @@ export const SteerWithdrawRequestSchema = z.object({ session_id: SessionIdSchema
330
395
  /** `withdrawn: false` when the steer is unknown, already taken by the Agent (it then settles as consumed), or being sent. */
331
396
  export const SteerWithdrawResponseSchema = z.object({ withdrawn: z.boolean(), text: z.string().optional() });
332
397
  export const SteerSendNowRequestSchema = z.object({ session_id: SessionIdSchema, steer_id: z.string().min(1) });
333
- /** `redirected: false` when nothing is live to deliver it to now; the steer stays pending. */
334
- export const SteerSendNowResponseSchema = z.object({ redirected: z.boolean() });
398
+ /** The server decides why Send now did or did not deliver. `redirected` remains for older clients. */
399
+ export const SteerSendNowResponseSchema = z.object({ redirected: z.boolean(), outcome: z.enum(['delivered', 'next_step', 'not_running', 'taken', 'failed']) });
335
400
  /** `text` is delivered to the running agent; `display_text` is what the transcript shows. */
336
401
  export const SteerRequestSchema = z.looseObject({ session_id: SessionIdSchema, text: z.string().min(1), display_text: z.string().optional(), steer_id: z.string().optional() });
337
402
  /** `accepted: false` with a `fallback` reason means the message was not delivered; the caller keeps the draft. */
338
403
  /** TAL-460: a steer sent while a background turn runs starts the user's own turn instead; `started_turn` is its start response. */
339
404
  export const SteerResponseSchema = z.looseObject({ accepted: z.boolean(), fallback: NullableString.optional(), stream_id: NullableString.optional(), steer_id: z.string().optional(), started_turn: ChatStartResponseSchema.optional() });
340
405
  export const ApprovalPendingSchema = z.looseObject({
406
+ allowed_choices: z.array(z.enum(['once', 'session', 'always', 'deny'])).optional(), can_enable_yolo: z.boolean().optional(),
341
407
  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(),
342
408
  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(),
343
409
  });
@@ -350,14 +416,34 @@ export const ClarifyChoiceSchema = z.union([z.string(), z.looseObject({ label: z
350
416
  export const ClarifyStepSchema = z.looseObject({ qid: z.string(), question: z.string(), choices: z.array(z.string()), multi_select: z.boolean() });
351
417
  /** Keyed step answers; a multi-select step takes a list. The server shapes them into the Agent's reply. */
352
418
  export const ClarifyAnswersSchema = z.record(z.string(), z.union([z.string(), z.array(z.string())]));
419
+ /** TAL-887: one input of a password-manager prompt, answered under its `key`; a `secure` input is masked. */
420
+ export const VaultPromptFieldSchema = z.object({ key: z.enum(['master_password', 'identifier', 'password', 'code', 'secret']), secure: z.boolean() });
421
+ /**
422
+ * TAL-887: the Agent asks for a password manager's master password (`vault_unlock`), a login to save for the page it is
423
+ * on (`vault_save_login`), a one-time code (`vault_code`), or a skill environment secret (`skill_secret`). Clients answer through
424
+ * `/api/clarify/respond` with keyed `answers`, or `decline`; an empty answer, a decline, a cancel and `timeout_at` all
425
+ * resolve it unanswered. Answers never enter chat, logs, streams, or responses. Skill secrets are saved only to the invoking profile.
426
+ */
427
+ export const VaultPromptSchema = z.object({
428
+ kind: z.enum(['vault_unlock', 'vault_save_login', 'vault_code', 'skill_secret']),
429
+ backend: z.string().nullable().describe('The password manager (`vault_unlock`).'),
430
+ display_name: z.string().nullable().describe('Its display name (`vault_unlock`).'),
431
+ origin: z.string().nullable().describe('The page origin the login is saved for (`vault_save_login`).'),
432
+ site: z.string().nullable().describe('The site, by host (`vault_save_login`, `vault_code`).'),
433
+ hint: z.string().nullable().describe('Where the code was sent, when the Agent says (`vault_code`).'),
434
+ skill: z.object({ env_var: z.string(), skill_name: z.string(), help: z.string().nullable(), required_for: z.string().nullable() }).optional().describe('Profile-local environment setup metadata (`skill_secret`).'),
435
+ fields: z.array(VaultPromptFieldSchema),
436
+ timeout_at: z.number().describe('Unix seconds when the Agent stops waiting.'),
437
+ });
353
438
  export const ClarifyPendingSchema = z.looseObject({
354
439
  steps: z.array(ClarifyStepSchema).optional(),
440
+ vault: VaultPromptSchema.optional(),
355
441
  clarify_id: z.string().optional(), session_id: z.string().optional(), question: z.string().optional(), description: z.string().optional(), choices: z.array(ClarifyChoiceSchema).optional(), title: z.string().optional(),
356
442
  name: z.string().optional(), kind: z.string().optional(), reason: z.string().optional(), action: z.string().optional(), status: z.string().optional(), raw_preview: z.string().optional(), timeout_at: z.number().optional(),
357
443
  timeout_seconds: z.number().optional(), pending_count: z.number().optional(), index: z.number().optional(), total: z.number().optional(),
358
444
  });
359
445
  export const ClarifyPendingEnvelopeSchema = z.looseObject({ pending: ClarifyPendingSchema.nullable(), pending_count: z.number().int() });
360
- export const ClarifyRespondRequestSchema = z.looseObject({ session_id: SessionIdSchema, answers: ClarifyAnswersSchema.optional(), response: z.string().optional(), answer: z.string().optional(), choice: z.string().optional(), clarify_id: z.string().optional() });
446
+ export const ClarifyRespondRequestSchema = z.looseObject({ session_id: SessionIdSchema, answers: ClarifyAnswersSchema.optional(), response: z.string().optional(), answer: z.string().optional(), choice: z.string().optional(), clarify_id: z.string().optional(), decline: z.boolean().optional() });
361
447
  export const ClarifyRespondResponseSchema = z.looseObject({ ok: z.boolean(), response: z.string().optional(), error: z.string().optional(), stale: z.boolean().optional() });
362
448
  export const DraftSchema = z.looseObject({ text: z.string(), files: z.array(Json) });
363
449
  export const DraftResponseSchema = z.looseObject({ ok: z.literal(true), draft: DraftSchema, draft_version: z.string().nullable(), unchanged: z.boolean().optional() });
@@ -410,7 +496,15 @@ export const SessionNewRequestSchema = z.looseObject({
410
496
  title: z.string().optional(), workspace: z.string().optional(), workspace_inherited_from_prev_session: z.boolean().optional(), model: z.string().nullable().optional(), model_provider: z.string().nullable().optional(),
411
497
  profile: z.string().nullable().optional(), prev_session_id: z.string().optional(), project_id: z.string().nullable().optional(), worktree: Json.optional(), enabled_toolsets: z.array(z.string()).nullable().optional(), parent_session_id: z.string().optional(),
412
498
  });
413
- export const TranscribeCapabilitySchema = z.looseObject({ ok: z.literal(true), available: z.boolean(), provider: z.string() });
499
+ /**
500
+ * `streaming` (TAL-802): live dictation over `TRANSCRIBE_STREAM_PATH` would open for this profile (`stt.streaming` on, a live provider wire).
501
+ * `warm_supported` (TAL-916): a client starting a recording should `POST /api/transcribe/warm` so the engine loads while the user speaks.
502
+ */
503
+ export const TranscribeCapabilitySchema = z.looseObject({ ok: z.literal(true), available: z.boolean(), provider: z.string(), streaming: z.boolean(), warm_supported: z.boolean() });
504
+ /** TAL-906: what read-aloud speaks for a message; clients play it as is. */
505
+ export const SpeechPrepareSchema = z.object({ spoken_text: z.string() });
506
+ /** `POST /api/transcribe`: the recording's transcript; `filtered` (TAL-917) when the server emptied a phrase Whisper invents on silent audio. */
507
+ export const TranscribeResponseSchema = z.looseObject({ ok: z.literal(true), transcript: z.string(), filtered: z.boolean() });
414
508
  // ── settings, profiles, models, providers ───────────────────────────────
415
509
  /** TAL-411: the request profile's quota urgency thresholds. A save patches valid fields and clamps critical to at most warning. */
416
510
  export const QuotaThresholdsSchema = z.object({
@@ -422,7 +516,7 @@ export const SettingsSchema = z.looseObject({
422
516
  bot_name: z.string().optional(), default_model: z.string().optional(), default_workspace: z.string().optional(), language: z.string().optional(), send_key: z.string().optional(), font_size: z.string().optional(),
423
517
  full_width_chat: z.boolean().optional(), chat_width: z.enum(['comfortable', 'wide', 'full']).optional(), auto_scroll_follow: z.boolean().optional(), render_user_markdown: z.boolean().optional(), chat_activity_display_mode: z.string().optional(), default_message_mode: z.string().optional(),
424
518
  fade_text_effect: z.boolean().optional(), hidden_tabs: z.array(z.string()).optional(), composer_control_order: z.array(z.string()).optional(), show_cli_sessions: z.boolean().optional(), show_claude_code_sessions: z.boolean().optional(),
425
- show_cron_sessions: z.boolean().optional(), show_webhook_sessions: z.boolean().optional(), show_kanban_sessions: z.boolean().optional(), check_for_updates: z.boolean().optional(), ignore_agent_updates: z.boolean().optional(),
519
+ show_cron_sessions: z.boolean().optional(), show_webhook_sessions: z.boolean().optional(), show_kanban_sessions: z.boolean().optional(), check_for_updates: z.boolean().optional(), summarize_updates_automatically: z.boolean().optional(), ignore_agent_updates: z.boolean().optional(),
426
520
  auth_enabled: z.boolean().optional(), password_auth_enabled: z.boolean().optional(), password_env_var: z.boolean().optional(), passkeys_enabled: z.boolean().optional(), passwordless_enabled: z.boolean().optional(),
427
521
  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(),
428
522
  max_tokens: NullableNumber.optional(), max_tokens_effective: NullableNumber.optional(), max_tokens_fallback: NullableNumber.optional(), tts_engine: z.string().optional(), tts_voice: z.string().optional(),
@@ -434,20 +528,47 @@ export const SettingsSchema = z.looseObject({
434
528
  export const ProfileSchema = z.looseObject({
435
529
  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(),
436
530
  total_skills: z.number().optional(), enabled_skills: z.number().optional(), gateway_running: z.boolean().optional(), has_env: z.boolean().optional(), visible: z.boolean().optional(),
531
+ /** TAL-805: the profile's gateway mode; `gateway_running` is true exactly for `own` and `served`. */
532
+ gateway_mode: GatewayModeSchema.optional(),
437
533
  /** TAL-213: Bot Mode identity from `profile.yaml` (the bot's roster title, else the profile's display name), one bounded printable line each. */
438
534
  display_name: z.string().optional(), description: z.string().optional(), has_avatar: z.boolean().optional(),
439
535
  /** TAL-213: present only when the profile has a canonical Bot Chat; clients open `tip_session_id` after switching to the profile. */
440
536
  canonical_session: CanonicalBotChatSchema.nullable().optional(),
537
+ /** TAL-819: skill and memory writes the Agent staged for approval, reviewed on the Skills and Memory pages. */
538
+ pending_writes: z.number().int().optional(),
441
539
  });
442
540
  export const ProfilesSchema = z.looseObject({ profiles: z.array(ProfileSchema), active: z.string(), single_profile_mode: z.boolean().optional() });
443
541
  export const ActiveProfileSchema = z.looseObject({ name: z.string(), path: z.string().optional(), is_default: z.boolean().optional(), default_workspace: NullableString.optional() });
444
542
  export const ProfileSwitchSchema = ProfilesSchema.extend({ is_default: z.boolean(), default_model: Json, default_model_provider: Json, default_workspace: z.string().nullable() });
445
543
  /** `/api/reasoning`: config.yaml agent.reasoning_effort / display.show_reasoning, resolved for a model. */
446
544
  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() });
447
- /** 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. */
448
- export const ModelEntrySchema = z.looseObject({ id: z.string(), label: z.string().optional(), provider: z.string().optional(), provider_id: z.string().optional(), bare_id: z.string().optional(), supports_fast_tier: z.boolean().optional() });
449
- export const ModelGroupSchema = z.looseObject({ provider: z.string(), provider_id: z.string().optional(), models: z.array(ModelEntrySchema), extra_models: z.array(ModelEntrySchema).optional() });
450
- export const ModelsSchema = z.looseObject({ active_provider: NullableString.optional(), default_model: z.string().optional(), default_provider_id: NullableString.optional(), default_bare_id: z.string().optional(), default_option_id: NullableString.optional(), groups: z.array(ModelGroupSchema), aliases: z.record(z.string(), Json).optional(), configured_model_badges: z.record(z.string(), Json).optional() });
545
+ export const QuotaLevelSchema = z.enum(['healthy', 'warning', 'critical', 'stale', 'unavailable']);
546
+ /**
547
+ * 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.
548
+ * TAL-807: `limit` is present while the provider's credential pool cannot serve this model until `resets_at` (ISO-8601 UTC).
549
+ */
550
+ export const ModelEntrySchema = z.looseObject({
551
+ 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(), supported_speed_tiers: z.array(SpeedTierSchema).optional(),
552
+ limit: z.object({ scope: z.literal('model'), resets_at: z.string() }).optional(),
553
+ });
554
+ /** TAL-807: a group's rate limit: every pool account benched until `resets_at`, else the entry ids (`models`) cooling down until their reset. */
555
+ export const ModelGroupLimitSchema = z.discriminatedUnion('scope', [
556
+ z.object({ scope: z.literal('account'), resets_at: z.string() }),
557
+ z.object({ scope: z.literal('models'), models: z.record(z.string(), z.string()) }),
558
+ ]);
559
+ /**
560
+ * TAL-807: `limit` is the provider's live pool rate limit; `quota_level` is the TAL-411 `remaining` level of the tightest
561
+ * account-scope window in the provider's last account-usage read (a model-family window never counts), absent without one
562
+ * and while the group is account-limited.
563
+ */
564
+ export const CatalogAvailabilitySchema = z.object({ status: z.enum(['available', 'stale', 'unavailable']), authoritative: z.boolean(), unavailable_reason: NullableString, generation: z.string().min(1).describe('Opaque source generation; cached data from another generation must never be merged.') });
565
+ export const ModelGroupSchema = z.looseObject({
566
+ availability: CatalogAvailabilitySchema.optional(),
567
+ provider: z.string(), provider_id: z.string().optional(), models: z.array(ModelEntrySchema), extra_models: z.array(ModelEntrySchema).optional(),
568
+ limit: ModelGroupLimitSchema.optional(), quota_level: QuotaLevelSchema.optional(),
569
+ });
570
+ export const ModelsSchema = z.looseObject({ availability: CatalogAvailabilitySchema.optional(), 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() });
571
+ export const ModelsLiveSchema = z.looseObject({ availability: CatalogAvailabilitySchema.optional(), provider: z.string().optional(), source: z.string().optional(), models: z.array(ModelEntrySchema), count: z.number().optional(), error: z.string().optional() });
451
572
  /** TAL-603: where a card's credential comes from; `other` is a source the server cannot name, shown as its raw `key_source`. */
452
573
  export const KeySourceKindSchema = z.enum(['env', 'oauth', 'config', 'pool', 'plugin', 'other']);
453
574
  /**
@@ -461,8 +582,7 @@ export const ProviderSchema = z.looseObject({
461
582
  is_custom: z.boolean().optional(), key_source: z.string().optional(), base_url: NullableString.optional(), auth_error: NullableString.optional(), env_var: NullableString.optional(), models: z.array(ModelEntrySchema).optional(), models_total: z.number().optional(),
462
583
  is_active: z.boolean().optional(), key_source_kind: KeySourceKindSchema.nullable().optional(), removable: z.boolean().optional(),
463
584
  });
464
- export const ProvidersSchema = z.looseObject({ providers: z.array(ProviderSchema), active_provider: NullableString.optional() });
465
- export const QuotaLevelSchema = z.enum(['healthy', 'warning', 'critical', 'stale', 'unavailable']);
585
+ export const ProvidersSchema = z.looseObject({ availability: CatalogAvailabilitySchema.optional(), providers: z.array(ProviderSchema), active_provider: NullableString.optional() });
466
586
  /** TAL-411: the server's classification per colour basis; `/api/provider/quotas` only, at the profile's thresholds. */
467
587
  export const QuotaUrgencySchema = z.looseObject({ remaining: QuotaLevelSchema, pace: QuotaLevelSchema.describe('Burn rate and pace tolerance; the remaining classification when the window has no pace.') });
468
588
  /** TAL-409: a window's pace as of the envelope's `computed_at`. Stale once `valid_until` (the window's reset) passes. */
@@ -487,6 +607,7 @@ export const QuotaWindowSchema = z.looseObject({
487
607
  });
488
608
  const QuotaWindowIndex = z.number().int().nullable().optional();
489
609
  export const QuotaSourceSchema = z.looseObject({
610
+ computed_at: z.string().optional().describe('Original reference time of this source, retained with stale windows.'),
490
611
  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(),
491
612
  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(),
492
613
  pace_window_index: QuotaWindowIndex.describe('The window a pace-coloured widget shows: the weekly one, else the first 5h/session window.'),
@@ -507,8 +628,8 @@ export const ProviderQuotaSchema = z.looseObject({
507
628
  account_limits: QuotaAccountLimitsSchema.nullable().optional(),
508
629
  });
509
630
  /** Python `get_provider_quotas`: the stable identity envelope the iOS quota widget persists (`scope_id`/`profile_id`). */
510
- export const ProviderQuotasSchema = z.looseObject({
511
- 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,
631
+ export const ProviderQuotasSchema = z.looseObject({ availability: CatalogAvailabilitySchema.optional(),
632
+ version: z.number(), computed_at: z.string().describe('ISO-8601 UTC request reference time; coalesced or stale sources retain their own computed_at.'), scope_id: z.string(), profile_id: z.string(), active_provider: NullableString, requested_source_id: NullableString,
512
633
  missing_source: z.boolean().describe('True when `?source=` names a source id this scope no longer has; `sources` is then empty.'),
513
634
  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.'),
514
635
  });
@@ -543,8 +664,12 @@ export const AuxiliaryTaskSchema = z.looseObject({
543
664
  selected_option_id: z.string().nullable(),
544
665
  /** False only for a pinned model the current catalog does not list; it stays visible and editable. */
545
666
  in_catalog: z.boolean(),
667
+ /** TAL-800: the slot a plugin task inherits its route from; while the task is Auto, `value_label` names that slot's model. */
668
+ inherit_from: z.string().nullable(),
669
+ /** TAL-800: the reasoning effort the slot runs with (the Agent's default until one is saved; `''` = provider default); null when the Agent does not report its slots. */
670
+ reasoning_effort: z.string().nullable(),
546
671
  });
547
- 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) });
672
+ 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(), service_tier: z.enum(['', 'default', 'priority', 'ultrafast']).optional().describe('Empty string is the Standard value returned by older servers.'), supported_speed_tiers: z.array(SpeedTierSchema).optional() }), tasks: z.array(AuxiliaryTaskSchema) });
548
673
  export const MaxTokensSchema = z.looseObject({ max_tokens: NullableNumber, max_tokens_effective: NullableNumber, max_tokens_fallback: NullableNumber });
549
674
  // ── workspaces, files, git ───────────────────────────────────────────────
550
675
  /** 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). */
@@ -567,18 +692,20 @@ export const FileContentSchema = z.looseObject({
567
692
  const GitBadgeSchema = z.looseObject({ is_git: z.boolean().optional(), branch: NullableString.optional(), dirty: z.number().optional(), modified: z.number().optional(), untracked: z.number().optional(), ahead: z.number().optional(), behind: z.number().optional() });
568
693
  export const GitInfoSchema = z.looseObject({ git: GitBadgeSchema.nullable().optional() });
569
694
  // ── tools ────────────────────────────────────────────────────────────────
570
- export const SkillSchema = z.looseObject({ name: z.string(), description: NullableString.optional(), category: NullableString.optional(), disabled: z.boolean().optional() });
695
+ export const SkillSchema = z.looseObject({ ...SkillCatalogIdentitySchema.shape, name: z.string(), description: NullableString.optional(), category: NullableString.optional(), disabled: z.boolean().optional() });
571
696
  export const SkillsSchema = z.looseObject({ skills: z.array(SkillSchema), categories: z.array(Json).optional() });
572
697
  /**
573
698
  * `linked_files` groups the skill directory's files by folder (`references`, `templates`, ...); open one with `?file=`.
574
699
  * A linked file that is not UTF-8 text carries `binary: true` and its `size` instead of `content`.
575
700
  */
576
- export const SkillContentSchema = z.looseObject({ name: z.string().optional(), content: z.string().optional(), path: z.string().optional(), linked_files: z.record(z.string(), z.array(z.string())).optional(), binary: z.literal(true).optional(), size: z.number().optional(), success: z.boolean().optional(), message: z.string().optional(), ok: z.boolean().optional() });
701
+ export const SkillContentSchema = z.looseObject({ editable: z.boolean().optional(), name: z.string().optional(), content: z.string().optional(), path: z.string().optional(), linked_files: z.record(z.string(), z.array(z.string())).optional(), binary: z.literal(true).optional(), size: z.number().optional(), success: z.boolean().optional(), message: z.string().optional(), ok: z.boolean().optional() });
577
702
  export const SkillsUsageSchema = z.looseObject({ usage: z.record(z.string(), z.looseObject({ use_count: z.number().optional(), view_count: z.number().optional(), patch_count: z.number().optional() })), skill_names: z.array(z.string()).optional(), total_invocations: z.number().optional(), unique_skills_used: z.number().optional() });
578
703
  export const MemorySchema = z.looseObject({
579
704
  memory: z.string(), user: z.string(), soul: z.string(), project_context: z.string().optional(), memory_path: z.string().optional(), user_path: z.string().optional(), soul_path: z.string().optional(), project_context_path: z.string().optional(),
580
705
  project_context_name: z.string().optional(), project_context_workspace: z.string().optional(), memory_mtime: NullableNumber.optional(), user_mtime: NullableNumber.optional(), soul_mtime: NullableNumber.optional(), project_context_mtime: NullableNumber.optional(),
581
706
  project_context_shadowed: z.array(Json).optional(), external_notes_enabled: z.boolean().optional(),
707
+ /** TAL-784: the revision of each file as this read found it. A save sends the revision its edit started from. */
708
+ memory_revision: z.string(), user_revision: z.string(), soul_revision: z.string(),
582
709
  });
583
710
  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() });
584
711
  export const PromptsSchema = z.looseObject({ prompts: z.array(PromptSchema) });
@@ -588,12 +715,15 @@ export const PromptsSchema = z.looseObject({ prompts: z.array(PromptSchema) });
588
715
  * clients can run it, and `unsupported_message` is the English text a client outside `clients` shows when it is typed.
589
716
  * Clients suggest the entries listing them whose name or any alias starts with the typed text (case-insensitive), and
590
717
  * resolve a typed alias to the entry's `name`. `exec` marks an Agent entry `POST /api/commands/exec` runs: the client
591
- * posts the typed text there and shows the returned output instead of starting a chat turn (TAL-561).
718
+ * posts the typed text there with the open chat's `session_id` and shows the returned output instead of starting a chat turn
719
+ * (TAL-561); `/refine` reviews that chat (TAL-902).
720
+ * `handler: 'prompt'` marks an Agent prompt built-in: the client sends the typed text as a chat turn and the server starts the
721
+ * turn with the Agent's expansion of it (TAL-817).
592
722
  */
593
723
  export const CommandClientSchema = z.enum(['web', 'ios']);
594
724
  export const CommandRowSchema = z.looseObject({
595
725
  name: z.string(), description: z.string().optional(), aliases: z.array(z.string()), args_hint: z.string().optional(), category: z.string().optional(),
596
- handler: z.enum(['client', 'agent']), clients: z.array(CommandClientSchema), unsupported_message: z.string().optional(),
726
+ handler: z.enum(['client', 'agent', 'prompt']), clients: z.array(CommandClientSchema), unsupported_message: z.string().optional(),
597
727
  cli_only: z.boolean().optional(), gateway_only: z.boolean().optional(), subcommands: z.array(Json).optional(), exec: z.boolean().optional(),
598
728
  });
599
729
  export const CommandsSchema = z.looseObject({ commands: z.array(CommandRowSchema) });
@@ -609,9 +739,11 @@ export const DashboardStatusSchema = z.looseObject({ running: z.boolean(), enabl
609
739
  * Gateway status for the Settings banner (TAL-266). `running` and `configured` come from the Agent health probe; when it
610
740
  * is inconclusive, stale running metadata or known gateway sessions mark the gateway configured. `platforms` are the
611
741
  * messaging platforms of known gateway sessions; `last_active` is the gateway session index's ISO mtime while running, else "".
742
+ * TAL-805: `gateway_mode` is the active profile's mode and decides `running` when it is not null. It is null for a remote
743
+ * gateway, or for the root profile when the sidecar cannot answer; a named profile whose mode is unconfirmed reads `stopped`.
612
744
  */
613
745
  export const GatewayStatusSchema = z.object({
614
- running: z.boolean(), configured: z.boolean(), platforms: z.array(z.object({ name: z.string(), label: z.string() })), last_active: z.string(), session_count: z.number().int(),
746
+ running: z.boolean(), configured: z.boolean(), gateway_mode: GatewayModeSchema.nullable(), platforms: z.array(z.object({ name: z.string(), label: z.string() })), last_active: z.string(), session_count: z.number().int(),
615
747
  health: z.object({ state: NullableString, reason: NullableString, gateway_state: NullableString }),
616
748
  });
617
749
  export const GatewayActionSchema = z.object({ ok: z.literal(true), action: z.enum(['start', 'stop', 'restart']), message: z.string(), status: GatewayStatusSchema });
@@ -627,21 +759,55 @@ export const ProjectOsDashboardSchema = z.object({
627
759
  handoff: z.record(z.string(), Json).nullable(), active: z.record(z.string(), Json).nullable(), heartbeat: z.record(z.string(), Json).nullable(),
628
760
  onboarding: z.looseObject({ active: z.boolean(), doc_source: z.enum(['project-os', 'root']) }).optional(), goal_summary: z.string(),
629
761
  });
630
- export const AgentHealthSchema = z.looseObject({ alive: z.boolean().nullable().optional(), checked_at: z.string().optional(), details: z.looseObject({ state: z.string().optional(), reason: z.string().optional() }).optional(), gateway_chat: z.looseObject({ enabled: z.boolean().optional(), backend: z.string().optional(), base_url_configured: z.boolean().optional(), api_key_configured: z.boolean().optional() }).optional(), error: z.string().optional() });
762
+ /** One server projection used by chat readiness, HTTP refusals, and persisted stream errors. */
763
+ export const AgentRuntimeMetadataSchema = z.object({
764
+ agent_runtime_reason: z.enum(['update_active', 'recovery_incomplete', 'source_changed', 'unknown']).optional(),
765
+ agent_update_state: z.string().optional(),
766
+ retryable: z.boolean().optional(),
767
+ restart_scheduled: z.boolean().optional(),
768
+ });
769
+ /** TAL-951: whether Web chat can run, from the Agent sidecar's status; `condition` says why not, null when available. */
770
+ export const ChatReadinessSchema = z.discriminatedUnion('available', [
771
+ z.object({ available: z.literal(true), condition: z.null() }),
772
+ z.object({ available: z.literal(false), condition: z.enum(['sidecar_unavailable', 'agent_incompatible', 'agent_runtime_stale']), ...AgentRuntimeMetadataSchema.shape }),
773
+ ]);
774
+ /** Messaging-gateway liveness (`alive`, `details`, `gateway_chat`) for Settings → System, plus Web chat readiness in `chat`. */
775
+ export const AgentHealthSchema = z.looseObject({ chat: ChatReadinessSchema.optional().describe('Always sent since TAL-951; absent from older servers.'), alive: z.boolean().nullable().optional(), checked_at: z.string().optional(), details: z.looseObject({ state: z.string().optional(), reason: z.string().optional() }).optional(), gateway_chat: z.looseObject({ enabled: z.boolean().optional(), backend: z.string().optional(), base_url_configured: z.boolean().optional(), api_key_configured: z.boolean().optional() }).optional(), error: z.string().optional() });
631
776
  export const SystemHealthSchema = z.looseObject({
632
777
  available: z.boolean().optional(), status: z.string().optional(), checked_at: z.string().optional(), cpu: Json.nullable().optional(), memory: Json.nullable().optional(), disk: z.looseObject({ percent: z.number().optional(), total_bytes: z.number().optional(), used_bytes: z.number().optional() }).nullable().optional(),
633
778
  errors: z.array(z.looseObject({ code: z.string().optional(), metric: z.string().optional() })).optional(), webui_runtime: Json.optional(),
634
779
  });
635
780
  export const UpdateStateSchema = z.enum(['up_to_date', 'release_ready', 'commits_behind', 'local_changes', 'finish', 'manual', 'check_failed', 'off', 'unknown']);
636
- export const UpdateTargetSchema = z.looseObject({ name: z.string().optional(), channel: z.enum(['stable', 'experimental']).optional(), unsupported: z.boolean().optional(), supported_revision: z.string().optional(), supported_version: z.string().optional(), candidate_revision: z.string().optional(), behind: z.number().nullable().optional(), current_sha: z.string().nullable().optional(), latest_sha: z.string().nullable().optional(), compare_url: z.string().optional(), repo_url: z.string().optional(), error: z.string().optional(), ok: z.boolean().optional(), manual_update: z.boolean().optional(), no_git: z.boolean().optional(), install_kind: z.literal('npm').optional(), npm: z.string().optional(), channel_switch: z.boolean().optional(), dirty: z.boolean().optional(), metadata_repair: z.boolean().optional(), release_url: z.string().optional(), ignored: z.boolean().optional(), stale_check: z.boolean().optional(), current_version: z.string().optional(),
781
+ const AgentSupportSchema = z.enum(['supported', 'unchecked']);
782
+ export const UpdateVersionSchema = z.object({
783
+ version: z.string().describe('The release as shown: the Agent tag or the Web release tag.'),
784
+ ref: z.string().describe('What `updates.apply`/`updates.force` accept back as `version`: the Agent tag or the Web semver.'),
785
+ sha: z.string().optional().describe('Agent only: the commit the tag peels to.'),
786
+ release_url: z.string().nullable(),
787
+ latest: z.boolean().describe('The channel\'s newest release, the one an update without `version` installs.'),
788
+ folded: z.boolean().describe('Shown behind the folded "more versions" node: every entry except the newest.'),
789
+ support: AgentSupportSchema.optional().describe('Agent only: this release\'s classification against the verified Agent window.'),
790
+ support_label: z.string().optional().describe('Agent only: `support` worded to fit one phone line.'),
791
+ }).describe('TAL-946: one installable Stable release newer than the installed one.');
792
+ export const UpdateTargetSchema = z.looseObject({
793
+ failed_update: z.object({ version: z.string(), restored_version: z.string() }).optional().describe('Web only: the update that failed before HTTP readiness and the restored package version. Manual retry remains available.'),
794
+ name: z.string().optional(), channel: z.enum(['stable', 'experimental']).optional(),
795
+ unsupported: z.boolean().optional().describe('Deprecated (TAL-350): `support === \'unchecked\'`, kept for older clients.'),
796
+ support: AgentSupportSchema.optional().describe('Agent target only (TAL-350): `supported` when the target revision is exactly a release in the verified Agent window, else `unchecked`; an `unchecked` apply requires `confirmed_agent_revision`.'),
797
+ support_label: z.string().optional().describe('Agent target only (TAL-946): `support` worded to fit one phone line.'),
798
+ current_support: AgentSupportSchema.optional().describe('Agent target only: the same classification for the installed Agent revision.'),
799
+ supported_revision: z.string().optional(), supported_version: z.string().optional(), candidate_revision: z.string().optional(), behind: z.number().nullable().optional(), current_sha: z.string().nullable().optional(), latest_sha: z.string().nullable().optional(), compare_url: z.string().optional(), repo_url: z.string().optional(), error: z.string().optional(), ok: z.boolean().optional(), manual_update: z.boolean().optional(), no_git: z.boolean().optional(), install_kind: z.literal('npm').optional(), npm: z.string().optional(), channel_switch: z.boolean().optional(), dirty: z.boolean().optional(), metadata_repair: z.boolean().optional(), release_url: z.string().optional(), ignored: z.boolean().optional(), stale_check: z.boolean().optional(), current_version: z.string().optional(),
637
800
  state: UpdateStateSchema.describe('TAL-559: the status clients word; `off` when checks are disabled or Agent updates are ignored.'),
638
801
  can_apply: z.boolean().describe('The apply action is available for this target.'),
639
802
  installed_unverified: z.boolean().describe('`release_ready` only: the installed revision sits on no release tag.'),
640
803
  manual_link: z.boolean().describe('Show the manual-install (releases) link.'),
641
804
  target_version: z.string().nullable().describe('The version the update path leads to, when one is shown.'),
642
805
  summary_available: z.boolean().describe('TAL-759: offer "Summarize with AI" for this target (`release_ready` or `commits_behind`).'),
806
+ versions: z.array(UpdateVersionSchema).describe('TAL-946: every installable Stable release newer than the installed one, newest first; empty unless `release_ready` can apply.'),
807
+ folded_count: z.number().int().nonnegative().describe('How many `versions` are `folded`.'),
643
808
  });
644
- export const UpdatesCheckSchema = z.looseObject({ disabled: z.boolean().optional(), cached: z.boolean().optional(), channel: z.string().optional(), agent_channel: z.enum(['stable', 'experimental']).optional(), checked_at: z.number().optional(), include_agent: z.boolean().optional(), webui: UpdateTargetSchema.nullable().optional(), agent: UpdateTargetSchema.nullable().optional() });
809
+ export const AgentRecoveryStateSchema = z.object({ phase: z.enum(['idle', 'running', 'waiting', 'complete', 'blocked']), attempt: z.number().int().nonnegative(), detail: z.string(), retry_at: z.string().nullable() });
810
+ export const UpdatesCheckSchema = z.looseObject({ agent_recovery: AgentRecoveryStateSchema.optional(), 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() });
645
811
  export const UpdatesSummarySchema = z.object({
646
812
  ok: z.boolean(), target: z.enum(['webui', 'agent']), summary: z.string(),
647
813
  current_version: z.string().nullable().describe('The installed version this summary starts from.'),
@@ -651,8 +817,12 @@ export const UpdatesSummarySchema = z.object({
651
817
  source: z.enum(['release_notes', 'commits', 'none']).describe('Stable targets summarize the release notes they install; commit-tracking targets, or unreadable notes, use commit subjects.'),
652
818
  cached: z.boolean(),
653
819
  });
654
- export const UpdateApplySchema = z.looseObject({ ok: z.boolean().optional(), status: z.string().optional(), error: z.string().optional(), message: z.string().optional(), lock: Json.optional(), confirmation_required: z.boolean().optional(), candidate_revision: z.string().optional(), supported_revision: z.string().optional(), supported_version: z.string().optional(), agent_channel: z.enum(['stable', 'experimental']).optional(), notification_id: z.uuid().optional(), channel_switch: z.boolean().optional(), backup_dir: z.string().nullable().optional(), backup_failed: z.boolean().optional(), stash_conflict: z.boolean().optional() });
655
- export const UpdateNotificationPhaseSchema = z.enum(['applying', 'awaiting_confirmation', 'restarting', 'succeeded', 'blocked', 'failed', 'unknown']);
820
+ export const UpdateApplySchema = z.looseObject({
821
+ recovery_compatibility: z.object({ manual_execution: z.boolean(), bot_chat_delivery: z.boolean() }).optional()
822
+ .describe('Whether the target Agent can reconcile retained manual execution and pending Bot Chat records. A false value blocks the update before any source or repair-intent change.'),
823
+ ok: z.boolean().optional(), status: z.string().optional(), error: z.string().optional(), message: z.string().optional(), lock: Json.optional(), confirmation_required: z.boolean().optional(), refresh_confirmation: AgentRefreshConfirmationSchema.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(), gateway_restart: z.string().optional().describe('TAL-953: `waiting_for_idle` when an Agent update left the gateway restart to wait for the gateway\'s running work; the update notification reports its outcome.')
824
+ });
825
+ export const UpdateNotificationPhaseSchema = z.enum(['applying', 'awaiting_confirmation', 'waiting_for_idle', 'restarting', 'succeeded', 'blocked', 'failed', 'unknown']);
656
826
  export const UpdateNotificationActionSchema = z.object({
657
827
  id: z.string().min(1).max(64).regex(/^[a-z0-9][a-z0-9_-]*$/),
658
828
  label: z.string().min(1).max(80),
@@ -710,10 +880,58 @@ export const UpdateNotificationsSchema = z.object({
710
880
  clearable_count: z.number().int().nonnegative(),
711
881
  can_clear: z.boolean(),
712
882
  });
713
- export const PluginSchema = z.looseObject({ key: z.string(), name: z.string().optional(), kind: z.string().optional(), enabled: z.boolean().optional(), description: z.string().optional(), version: z.string().optional(), activation: z.string().optional(), is_active_provider: z.boolean().optional(), hooks: z.array(Json).optional() });
714
- export const PluginsSchema = z.looseObject({ plugins: z.array(PluginSchema), empty: z.boolean().optional(), read_only: z.boolean().optional(), supported_hooks: z.array(z.string()).optional(), unavailable: z.boolean().optional() });
883
+ /** TAL-806: a plugin setting as Settings shows it; `locked` marks a secret the server's launch environment supplies, so an edit would not take effect. */
884
+ export const PluginSettingViewSchema = PluginSettingFieldSchema.extend({ locked: z.boolean().optional() });
885
+ export const PluginSchema = z.looseObject({ key: z.string(), name: z.string().optional(), kind: z.string().optional(), enabled: z.boolean().optional(), description: z.string().optional(), version: z.string().optional(), activation: z.string().optional(), is_active_provider: z.boolean().optional(), hooks: z.array(Json).optional(),
886
+ /** TAL-806: the manifest's `config_schema` fields with current values; absent when the plugin declares none. */
887
+ settings: z.array(PluginSettingViewSchema).optional() });
888
+ /**
889
+ * `read_only` (TAL-806): this session may not save plugin `settings` (not the server owner, or no sidecar).
890
+ * `plugin_isolation_advisory` (TAL-820): more than one profile has third-party plugins installed and one of those runs them
891
+ * `in_process`, so Settings recommends `plugins.isolation: host`; Talaria never sets the mode.
892
+ */
893
+ export const PluginsSchema = z.looseObject({ plugins: z.array(PluginSchema), empty: z.boolean().optional(), read_only: z.boolean().optional(), supported_hooks: z.array(z.string()).optional(), unavailable: z.boolean().optional(), plugin_isolation_advisory: z.boolean().optional() });
715
894
  export const McpServerSchema = z.looseObject({ name: z.string(), id: z.string().optional(), transport: z.string().optional(), enabled: z.boolean().optional(), can_toggle: z.boolean().optional().describe('Whether PATCH can enable or disable this server; false for invalid config.'), active: z.boolean().optional(), status: z.string().optional(), tools: z.number().optional(), tool_count: z.number().int().nullable().optional(), health: z.string().optional(), health_detail: z.string().optional(), health_checked_at: Json.optional(), health_pending: z.boolean().optional() });
716
895
  export const McpServersSchema = z.looseObject({ servers: z.array(McpServerSchema), health_pending: z.boolean().optional(), reload_required: z.boolean().optional(), toggle_supported: z.boolean().optional() });
896
+ /** TAL-818: one connected account of a Nous tool-gateway app; `name` is its alias, else the vendor label. `problem` is its gateway status while it is not active, else null. */
897
+ export const ConnectorAccountViewSchema = z.object({ connection_id: z.string(), name: z.string(), problem: z.enum(['pending', 'failed', 'expired', 'revoked', 'inactive']).nullable(), status_reason: z.string().nullable(), active: z.boolean(), created_at: z.string() });
898
+ /**
899
+ * TAL-818: one app on the Connectors page. `accounts` are its live accounts, active first. `enabled`: Hermes may use it (the
900
+ * member rule allows it and no other layer locks it). `org_locked` means an organization or role rule keeps it off whatever the member rule says. `rules_editable`: the switch
901
+ * may change the member rule (readable rules and no lock). `tools_off` counts the tools the member rules turn off.
902
+ */
903
+ export const ConnectorViewSchema = z.object({
904
+ slug: z.string(), name: z.string(), description: z.string(), category: z.string(), logo_url: z.string().nullable(),
905
+ connected: z.boolean(), accounts: z.array(ConnectorAccountViewSchema), enabled: z.boolean(), org_locked: z.boolean(), rules_editable: z.boolean(), tools_off: z.number().int(),
906
+ });
907
+ /**
908
+ * TAL-818: the Connectors page. `available` is the Agent's connectors gate; false hides the page. `policy_revision` is the
909
+ * member rules' revision every rule change sends back; null when the rules are read-only (no member layer, or unreadable),
910
+ * so no switch changes them. `rules_error`: why the rules could not be read; every app then reads off (fails closed).
911
+ */
912
+ export const ConnectorsPageSchema = z.object({ available: z.boolean(), connectors: z.array(ConnectorViewSchema), policy_revision: z.string().nullable(), rules_error: z.string().nullable() });
913
+ /**
914
+ * TAL-818: one app's tools: `enabled`: Hermes may use it (the member rules leave it on and nothing locks it); `locked`: an
915
+ * organization or role rule (by name or tag) keeps it off. `editable`: its switch may change the member rule (readable rules, the app on in them, and no lock).
916
+ */
917
+ export const ConnectorToolsViewSchema = z.object({
918
+ connector: z.string(), policy_revision: z.string().nullable(),
919
+ tools: z.array(z.object({ slug: z.string(), name: z.string(), description: z.string(), facet: z.enum(['read', 'write', 'destructive', 'unclassified']), deprecated: z.boolean(), enabled: z.boolean(), locked: z.boolean(), editable: z.boolean() })),
920
+ });
921
+ /**
922
+ * TAL-818: a connection operation, from the Connectors page's Connect or a chat's `manage_connections` card. Each target's
923
+ * `connect_url` is the authorization link while it waits on one; `actions` are the moves the user may make on it now, and
924
+ * `env_fields` the credentials an `approve` sends, each with its `label`. `can_continue`: the card may end the wait for the
925
+ * rest (chat only). `outcome` is null while the operation is open, then `connected` when every target connected.
926
+ */
927
+ export const ConnectorOperationViewSchema = z.object({
928
+ op_id: z.string(), settled: z.boolean(), outcome: z.enum(['connected', 'not_connected']).nullable(), can_continue: z.boolean(), tool_call_id: z.string().nullable(),
929
+ targets: z.array(z.object({
930
+ name: z.string(), title: z.string(), kind: z.string(), state: z.enum(['pending', 'initiated', 'connected', 'skipped', 'failed', 'expired', 'not_connected']),
931
+ detail: z.string(), connect_url: z.string().nullable(), actions: z.array(z.enum(['skip', 'retry', 'approve'])),
932
+ env_fields: z.array(z.object({ name: z.string(), label: z.string(), required: z.boolean(), secret: z.boolean() })),
933
+ })),
934
+ });
717
935
  export const McpToolsSchema = z.looseObject({ tools: z.array(z.looseObject({ name: z.string().optional(), server: z.string().optional(), description: z.string().optional() })), total: z.number().int().optional(), source: z.string().optional(), inventory_scope: z.string().optional(), unavailable_servers: z.array(z.string()).optional() });
718
936
  export const NotesSourcesSchema = z.looseObject({ enabled: z.boolean().optional(), sources: z.array(Json).optional(), source: z.string().optional(), inventory_scope: z.string().optional(), recent_ai_notes: z.array(Json).optional(), attach_supported: z.boolean().optional(), automatic_recall_unchanged: z.boolean().optional() });
719
937
  /** Joplin search hit: bounded, redacted display text and a snippet around the query; `updated_time` is Joplin's raw value. */
@@ -767,12 +985,17 @@ export const CronJobViewSchema = z.looseObject({
767
985
  derived_state: CronDerivedStateSchema.optional(), needs_attention: z.boolean().optional(), resumable: z.boolean().optional(),
768
986
  /** Server-derived (TAL-599): a known cause clients explain in the needs-attention banner. */
769
987
  attention_hint: z.enum(['croniter_missing']).nullable(),
988
+ /** Server-normalized (TAL-810): the job's latest failed run, kept after later successes; `at` is epoch seconds, null when unparseable. */
989
+ last_failure: z.object({ at: z.number().nullable(), detail: z.string() }).nullable(),
770
990
  });
991
+ /** TAL-810: a listed profile's cron store whose writes fail (full disk, read-only mount, permissions), so its scheduler skips every run. `error` and `fix` are the Agent's text; `since` is the last write that landed (epoch seconds), null when unknown. */
992
+ export const CronStoreHealthSchema = z.object({ profile: z.string(), store: z.string(), error: z.string(), fix: z.string(), since: z.number().nullable() });
771
993
  export const CronsSchema = z.looseObject({
772
994
  jobs: z.array(CronJobViewSchema),
773
995
  /** TAL-601: `jobs` arrive in display order (running first, then soonest next run, none last, then name); clients keep it. Absent from older servers. */
774
996
  ordered: z.boolean().optional(),
775
997
  active_profile: z.string().optional(), all_profiles: z.boolean().optional(), other_profile_count: z.number().optional(), cron_unavailable: z.boolean().optional(),
998
+ unwritable_stores: z.array(CronStoreHealthSchema),
776
999
  });
777
1000
  export const CronContextSourcesSchema = z.object({ profile: z.string(), sources: z.array(z.object({ job_id: z.string(), label: z.string(), selectable: z.boolean() })) });
778
1001
  /** `GET /api/crons/recent` row: one job's latest completion, ordered and classified by the server. */
@@ -793,18 +1016,21 @@ export const KanbanTaskActionsSchema = z.object({ block: z.boolean(), unblock: z
793
1016
  /** Server-computed card policy on every task the Kanban routes return (TAL-557). `requires_running_exit_confirmation`: any action takes the task out of a live run, so clients confirm first. */
794
1017
  export const KanbanTaskPolicyShape = { available_actions: KanbanTaskActionsSchema, requires_running_exit_confirmation: z.boolean() };
795
1018
  export const KanbanTaskViewSchema = z.looseObject({ id: z.union([z.string(), z.number()]), title: z.string().optional(), status: z.string().optional(), assignee: NullableString.optional(), priority: z.union([z.string(), z.number()]).nullable().optional(), description: z.string().optional(), tags: z.array(z.string()).optional(), session_id: NullableString.optional(), archived: z.boolean().optional(), created_at: Json.optional(), updated_at: Json.optional(), ...KanbanTaskPolicyShape });
796
- export const KanbanColumnSchema = z.looseObject({ name: z.string(), tasks: z.array(KanbanTaskViewSchema) });
1019
+ /** A board column in the Agent workflow's order (TAL-796): `name` is the status key, `label` the Agent's English label. */
1020
+ export const KanbanColumnSchema = z.looseObject({ name: z.string(), label: z.string(), tasks: z.array(KanbanTaskViewSchema) });
797
1021
  /** One profile lane (TAL-567): an assignee's tasks (`null` = unassigned) across every board column, with its task count. */
798
1022
  export const KanbanLaneSchema = z.object({ assignee: z.string().nullable(), count: z.number().int(), columns: z.array(KanbanColumnSchema) });
799
1023
  export const KanbanBoardViewSchema = z.looseObject({ columns: z.array(KanbanColumnSchema).optional(), assignees: z.array(Json).optional(), filters: Json.optional(), latest_event_id: z.number().optional(), read_only: z.boolean().optional(), tenants: z.array(Json).optional(), changed: z.boolean().optional(),
800
1024
  /** Statuses a bulk status change may target (TAL-557). */
801
1025
  bulk_move_targets: z.array(z.string()),
1026
+ /** Statuses a new task may start in (TAL-796). */
1027
+ create_statuses: z.array(z.string()),
802
1028
  /** The saved board view (TAL-567): `true` groups the board into `lanes` by assignee. */
803
1029
  lane_by_profile: z.boolean(),
804
1030
  /** Present when `lane_by_profile`: the columns split into profile lanes, assignees in name order, unassigned last. */
805
1031
  lanes: z.array(KanbanLaneSchema).optional() });
806
1032
  export const KanbanBoardsViewSchema = z.looseObject({ boards: z.array(z.looseObject({ slug: z.string(), name: z.string().nullable().optional(), is_current: z.boolean().optional(), total: z.number().optional(), archived: z.boolean().optional(), color: z.string().optional(), icon: z.string().optional(), description: z.string().optional(),
807
- /** Whether the board can be archived or deleted; the default board cannot (TAL-567). */
1033
+ /** Whether the board can be archived or deleted; the default board (TAL-567) and an archived board (TAL-795) cannot. */
808
1034
  removable: z.boolean() })), current: z.string().optional(), read_only: z.boolean().optional() });
809
1035
  /** Board-wide task counts (TAL-567): `status_counts` in board-column order, unknown statuses after; `total` excludes archived tasks. */
810
1036
  export const KanbanStatsViewSchema = z.looseObject({ by_status: z.record(z.string(), z.number()), total: z.number().int(), status_counts: z.array(z.object({ status: z.string(), count: z.number().int() })) });