@parall/sdk 1.36.1 → 1.37.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.
package/src/types.ts CHANGED
@@ -774,12 +774,26 @@ export interface Machine {
774
774
  // foreground runs that can't load the overlay or restart after exit(42).
775
775
  // Reported on heartbeat; gates the "Check for Update" affordance + API.
776
776
  self_update_capable?: boolean;
777
+ // Daemon-reported runtime CLIs found on the host (heartbeat-refreshed).
778
+ // Absent/null = never reported (pre-detection daemon — UNKNOWN, do not warn);
779
+ // [] = detection ran and found nothing. A signal for pickers only — attach /
780
+ // create are never gated on it (a CLI can be installed at any time).
781
+ detected_runtimes?: DetectedRuntime[] | null;
777
782
  region: string | null;
778
783
  created_by: string;
779
784
  created_at: string;
780
785
  updated_at: string;
781
786
  }
782
787
 
788
+ // One entry of Machine.detected_runtimes. status 'ok' = the CLI resolved and
789
+ // executed (`--version` succeeded); 'broken' = it resolved on disk but failed
790
+ // to run (present-but-unusable install).
791
+ export interface DetectedRuntime {
792
+ runtime_type: string;
793
+ version?: string;
794
+ status: 'ok' | 'broken';
795
+ }
796
+
783
797
  export type DaemonWorkspaceMode = 'default' | 'local_path' | 'git';
784
798
  export type DaemonWorkspaceSetupRunOn = 'first_attach' | 'config_change';
785
799
 
@@ -846,7 +860,7 @@ export interface AgentProfile {
846
860
  model_management: string | null; // "platform" | "self" | null
847
861
  permissions: string[];
848
862
  machine_id: string | null;
849
- hosting_mode?: 'hosted' | 'self_hosted';
863
+ hosting_mode?: 'hosted' | 'self_hosted' | 'platform_runtime';
850
864
  runtime_type: string;
851
865
  description: string | null;
852
866
  thinking_effort: string | null;
@@ -922,6 +936,17 @@ export interface MachineConfigUpdatedData {
922
936
  provider_enabled?: boolean;
923
937
  }
924
938
 
939
+ // Per-agent counterpart of MachineConfigUpdatedData: ONE local agent's
940
+ // explicit llm_source changed, so the daemon respawns just that agent's
941
+ // child. llm_source carries the agent's new EFFECTIVE source (explicit
942
+ // per-agent choice, else the machine default). Mirrors the Go
943
+ // MachineAgentConfigUpdatedData struct.
944
+ export interface MachineAgentConfigUpdatedData {
945
+ machine_id: string;
946
+ agent_id: string;
947
+ llm_source: string;
948
+ }
949
+
925
950
  export interface MachineWorkspaceSetupRequestedData {
926
951
  machine_id: string;
927
952
  agent_id: string;
@@ -958,6 +983,14 @@ export interface MachineBrowserProfileLifecycleData {
958
983
  profile_id: string;
959
984
  action: BrowserProfileLifecycleAction;
960
985
  start_url?: string;
986
+ /** Profile's lifecycle_generation after this transition's bump (PR #1650). The
987
+ * daemon acts under it and echoes it in its status report so the server can
988
+ * reject a stale report. Absent/0 from a pre-generation server. */
989
+ generation?: number;
990
+ /** Profile's reset_generation after this transition (bumped only by `reset`). On
991
+ * an online reset the daemon records it in its per-profile sidecar so a later
992
+ * reconnect doesn't redundantly re-wipe. */
993
+ reset_generation?: number;
961
994
  }
962
995
 
963
996
  /**
@@ -1078,6 +1111,13 @@ export interface Task {
1078
1111
  canceled_at: string | null;
1079
1112
  archived_at?: string | null;
1080
1113
  deleted_at?: string | null;
1114
+ /**
1115
+ * Per-viewer: whether the requesting user may manage OTHER members' comment
1116
+ * subscriptions for this task (creator / assignee / project lead / org
1117
+ * owner-admin). Populated on single-task GET; undefined elsewhere. Read this to
1118
+ * gate subscriber-management UI — do not re-derive the rule client-side.
1119
+ */
1120
+ can_manage_subscribers?: boolean;
1081
1121
  }
1082
1122
 
1083
1123
  export interface TaskWatcher {
@@ -1537,6 +1577,19 @@ export interface Wiki {
1537
1577
  created_by: string;
1538
1578
  created_at: string;
1539
1579
  updated_at: string;
1580
+ /** Protected default wiki — cannot be deleted. */
1581
+ is_default?: boolean | null;
1582
+ /** Set when the wiki is soft-deleted (recoverable). */
1583
+ deleted_at?: string | null;
1584
+ /** User who soft-deleted the wiki. */
1585
+ deleted_by?: string | null;
1586
+ /** Set once purge is claimed — the wiki can no longer be restored. */
1587
+ purge_started_at?: string | null;
1588
+ /** deleted_at + retention window (server-computed) — the recycle-bin countdown
1589
+ * source. Past this, restore is rejected with 410 WIKI_RETENTION_EXPIRED. */
1590
+ purge_eligible_at?: string | null;
1591
+ /** Server-computed restore availability for recycle-bin rows. */
1592
+ restorable?: boolean | null;
1540
1593
  }
1541
1594
 
1542
1595
  export interface WikiFileChange {
@@ -1568,11 +1621,19 @@ export interface WikiTreeEntry {
1568
1621
  path: string;
1569
1622
  type: 'tree' | 'blob';
1570
1623
  size?: number;
1624
+ /**
1625
+ * Entry sits inside a read-gated private subtree (path restriction). Advisory
1626
+ * display flag — the caller already passed read authorization to receive the
1627
+ * entry; use it to badge private folders, never for access decisions.
1628
+ */
1629
+ restricted?: boolean;
1571
1630
  }
1572
1631
 
1573
1632
  export interface WikiTreeResponse {
1574
1633
  data: WikiTreeEntry[];
1575
1634
  path: string;
1635
+ /** Whether the listed directory itself sits inside a read-gated private subtree. */
1636
+ path_restricted?: boolean;
1576
1637
  }
1577
1638
 
1578
1639
  export interface WikiFileManifestEntry {
@@ -1835,6 +1896,11 @@ export interface CreateWikiPathRestrictionRequest {
1835
1896
  level?: 'read' | 'maintain' | 'admin';
1836
1897
  subjects: string[];
1837
1898
  apply_to_admins?: boolean;
1899
+ // When true, the server returns 409 RESTRICTION_EXISTS if a restriction already
1900
+ // exists at `path` instead of upserting its subjects. Set by the member "create
1901
+ // private space" flow so a stale/concurrent create can't silently overwrite an
1902
+ // existing space's share list; edit-access flows leave it unset (upsert).
1903
+ create_only?: boolean;
1838
1904
  }
1839
1905
 
1840
1906
  export interface WikiAccessStatus {
@@ -1881,6 +1947,24 @@ export interface WikiOperationsResponse {
1881
1947
  // Wiki Blob
1882
1948
  // ============================================================
1883
1949
 
1950
+ /**
1951
+ * Copy one wiki file to another path as an independent snapshot (no ongoing
1952
+ * sync). Primary use: "share a private file out" — copy from the caller's
1953
+ * personal namespace (`users/{userId}/…`) into the public wiki so the whole
1954
+ * org can read + edit the copy, leaving the private original untouched.
1955
+ */
1956
+ export interface CopyWikiFileRequest {
1957
+ source_path: string;
1958
+ dest_path: string;
1959
+ message?: string;
1960
+ }
1961
+
1962
+ export interface CopyWikiFileResponse {
1963
+ source_path: string;
1964
+ dest_path: string;
1965
+ commit_sha: string;
1966
+ }
1967
+
1884
1968
  export interface WikiBlob {
1885
1969
  path: string;
1886
1970
  ref: string;
@@ -1895,6 +1979,8 @@ export interface WikiBlob {
1895
1979
  content?: string;
1896
1980
  line_count?: number;
1897
1981
  sha256?: string;
1982
+ /** File sits inside a read-gated private subtree — advisory display flag. */
1983
+ restricted?: boolean;
1898
1984
  // Phase 2 additions for the binary response shape:
1899
1985
  mime?: string;
1900
1986
  is_lfs?: boolean;
@@ -2044,6 +2130,16 @@ export interface MembershipChangedData {
2044
2130
  action: 'added' | 'removed' | 'role_changed';
2045
2131
  }
2046
2132
 
2133
+ /**
2134
+ * A member was removed from an org — kicked by an admin or self-removal
2135
+ * ("leave org"). Delivered on org:{orgId} (remaining members refresh the
2136
+ * directory) and user:{userId} (the removed user's clients drop the org).
2137
+ */
2138
+ export interface OrgMemberRemovedData {
2139
+ org_id: string;
2140
+ user_id: string;
2141
+ }
2142
+
2047
2143
  // ---- Task Comment & Activity Types ----
2048
2144
 
2049
2145
  export type TaskActivityAction = 'created' | 'field_changed' | 'commented';
@@ -2186,6 +2282,14 @@ export interface AgentStepNewData extends AgentStep {}
2186
2282
  export type WikiChangesetCreatedData = WikiChangeset & { org_id: string };
2187
2283
  export type WikiChangesetUpdatedData = WikiChangeset & { org_id: string };
2188
2284
 
2285
+ export interface WikiDeletedData {
2286
+ org_id: string;
2287
+ wiki_id: string;
2288
+ slug: string;
2289
+ name: string;
2290
+ }
2291
+ export type WikiRestoredData = WikiDeletedData;
2292
+
2189
2293
  // Wiki Comment
2190
2294
 
2191
2295
  // Unified Comment (replaces TaskComment and WikiComment)
@@ -2263,6 +2367,134 @@ export interface InboxItem {
2263
2367
  updated_at: string;
2264
2368
  }
2265
2369
 
2370
+ // ============================================================
2371
+ // External IM Channel Types (platform-mediated Feishu/Slack channel)
2372
+ // ============================================================
2373
+
2374
+ export type ChannelProvider = 'feishu' | 'slack';
2375
+ export type ChannelConnectionStatus = 'pending' | 'active' | 'disabled';
2376
+ /**
2377
+ * Inbound transport for a connection. Defaults to the provider's lowest-
2378
+ * onboarding-cost mode server-side (Feishu → socket, Slack → webhook) when
2379
+ * omitted on create; can be overridden per connection.
2380
+ */
2381
+ export type ChannelIngressMode = 'webhook' | 'socket';
2382
+ export type ChannelMessageStatus = 'received' | 'dispatched' | 'failed';
2383
+
2384
+ export interface ChannelConnection {
2385
+ id: string;
2386
+ org_id: string;
2387
+ agent_id: string;
2388
+ provider: ChannelProvider;
2389
+ ingress_mode: ChannelIngressMode;
2390
+ display_name: string;
2391
+ created_by: string;
2392
+ status: ChannelConnectionStatus;
2393
+ external_app_id?: string;
2394
+ external_team_id?: string;
2395
+ archived_at?: string | null;
2396
+ archive_reason?: string | null;
2397
+ /** Returned once at mint time (create / token regeneration). */
2398
+ ingress_url?: string;
2399
+ /** Returned once at mint time (create / token regeneration). */
2400
+ ingress_token?: string;
2401
+ created_at: string;
2402
+ updated_at: string;
2403
+ }
2404
+
2405
+ export interface ChannelCredentialsInput {
2406
+ app_id: string;
2407
+ app_secret: string;
2408
+ verification_token?: string;
2409
+ encrypt_key?: string;
2410
+ team_id?: string;
2411
+ }
2412
+
2413
+ export interface CreateChannelConnectionInput {
2414
+ agent_id: string;
2415
+ provider: ChannelProvider;
2416
+ display_name: string;
2417
+ /** Override the provider's default inbound transport (optional). */
2418
+ ingress_mode?: ChannelIngressMode;
2419
+ /** Manual provisioning: activates the connection immediately. */
2420
+ credentials?: ChannelCredentialsInput;
2421
+ }
2422
+
2423
+ export interface UpdateChannelConnectionInput {
2424
+ display_name?: string;
2425
+ status?: 'active' | 'disabled';
2426
+ }
2427
+
2428
+ export interface ChannelConversation {
2429
+ id: string;
2430
+ org_id: string;
2431
+ connection_id: string;
2432
+ agent_id: string;
2433
+ agent_session_id?: string | null;
2434
+ external_conversation_id: string;
2435
+ external_thread_id: string;
2436
+ conversation_type: string;
2437
+ external_user_id: string;
2438
+ external_user_name: string;
2439
+ last_inbound_at?: string | null;
2440
+ created_at: string;
2441
+ updated_at: string;
2442
+ }
2443
+
2444
+ export interface ChannelMessage {
2445
+ id: string;
2446
+ org_id: string;
2447
+ connection_id: string;
2448
+ conversation_id: string;
2449
+ external_message_id: string;
2450
+ text: string;
2451
+ external_user_id: string;
2452
+ external_user_name: string;
2453
+ status: ChannelMessageStatus;
2454
+ received_at: string;
2455
+ created_at: string;
2456
+ }
2457
+
2458
+ /**
2459
+ * Feishu one-click provisioning session (OAuth device flow). The client
2460
+ * renders `verification_url` as a QR code and lazily polls the status
2461
+ * endpoint every `poll_interval_seconds` — each poll may forward one
2462
+ * provider poll server-side, so respect the interval.
2463
+ */
2464
+ export type ChannelProvisioningStatus =
2465
+ | 'pending'
2466
+ | 'polling'
2467
+ | 'done'
2468
+ | 'expired'
2469
+ | 'denied'
2470
+ | 'canceled'
2471
+ | 'failed';
2472
+
2473
+ export interface ChannelProvisioningSession {
2474
+ id: string;
2475
+ org_id: string;
2476
+ agent_id: string;
2477
+ provider: ChannelProvider;
2478
+ created_by: string;
2479
+ status: ChannelProvisioningStatus;
2480
+ poll_interval_seconds: number;
2481
+ /** Set when status = 'done': the minted connection. */
2482
+ connection_id?: string;
2483
+ /** Terminal detail: access_denied / expired_token / domain_switched / connection_exists / alias_conflict / internal. */
2484
+ error_code?: string;
2485
+ expires_at: string;
2486
+ created_at: string;
2487
+ updated_at: string;
2488
+ /** QR target — present on live (pending/polling) sessions only. */
2489
+ verification_url?: string;
2490
+ user_code?: string;
2491
+ }
2492
+
2493
+ export interface InitiateChannelProvisioningInput {
2494
+ agent_id: string;
2495
+ provider: ChannelProvider;
2496
+ }
2497
+
2266
2498
  // ============================================================
2267
2499
  // Dispatch Types (agent event delivery)
2268
2500
  // ============================================================
@@ -2275,7 +2507,8 @@ export type DispatchEventType =
2275
2507
  | 'wiki_comment'
2276
2508
  | 'schedule.fire'
2277
2509
  | 'external_trigger'
2278
- | 'approval_decided';
2510
+ | 'approval_decided'
2511
+ | 'channel_message';
2279
2512
  export type DispatchStatus = 'pending' | 'received' | 'acked';
2280
2513
  export type DispatchDeliveryReason = 'mention' | 'watcher' | 'assignee' | 'creator';
2281
2514
 
@@ -2398,10 +2631,13 @@ export type WsEventMap = {
2398
2631
  'invitation.revoked': InvitationRevokedEventData;
2399
2632
  'org.join_request.new': OrgJoinRequestNewEventData;
2400
2633
  'org.invite_link.joined': OrgInviteLinkJoinedEventData;
2634
+ 'org.member.removed': OrgMemberRemovedData;
2401
2635
  'agent_config.update': AgentConfigUpdateData;
2402
2636
  'presence.update': PresenceUpdateData;
2403
2637
  'wiki.changeset.created': WikiChangesetCreatedData;
2404
2638
  'wiki.changeset.updated': WikiChangesetUpdatedData;
2639
+ 'wiki.deleted': WikiDeletedData;
2640
+ 'wiki.restored': WikiRestoredData;
2405
2641
  'comment.created': CommentCreatedData;
2406
2642
  'comment.updated': CommentUpdatedData;
2407
2643
  'comment.deleted': CommentDeletedData;
@@ -2425,6 +2661,7 @@ export type WsEventMap = {
2425
2661
  'machine.agent.attached': MachineAgentAttachedData;
2426
2662
  'machine.agent.detached': MachineAgentDetachedData;
2427
2663
  'machine.config.updated': MachineConfigUpdatedData;
2664
+ 'machine.agent_config.updated': MachineAgentConfigUpdatedData;
2428
2665
  'machine.workspace.setup.requested': MachineWorkspaceSetupRequestedData;
2429
2666
  'machine.stop': MachineStopData;
2430
2667
  'machine.filesystem.browse': MachineFilesystemBrowseData;
@@ -2494,7 +2731,14 @@ export interface PlatformConfigResponse {
2494
2731
  version: string;
2495
2732
  schema_version: number;
2496
2733
  min_plugin_version?: string;
2734
+ /** @deprecated V1-compat only — old bridges read this for override-vs-floor. New bridges read `model_is_pin`. */
2497
2735
  mode?: string;
2736
+ /**
2737
+ * Whether the delivered model is an operator PIN (override, beats the runtime's
2738
+ * env/default) vs a catalog FLOOR (loses to env). Optional: an old server omits
2739
+ * it and the bridge falls back to the legacy `model_management`-derived signal.
2740
+ */
2741
+ model_is_pin?: boolean;
2498
2742
  config: Record<string, unknown>;
2499
2743
  }
2500
2744
 
@@ -2843,6 +3087,61 @@ export interface ComputePricing {
2843
3087
  // component name ("cpu" | "memory" | "storage").
2844
3088
  export type ComputePricingResponse = Record<string, ComputePricing>;
2845
3089
 
3090
+ /**
3091
+ * Per-runtime capability row from GET /api/v1/runtimes (public, no auth).
3092
+ * Server SSOT: server/pkg/hosting/capabilities.go RuntimeCapability — the
3093
+ * data source for the create/settings interlock (which compute × intelligence
3094
+ * × model combinations are offerable per runtime).
3095
+ */
3096
+ export interface RuntimeCapability {
3097
+ runtime: string;
3098
+ /** Where the runtime can run: "hosted" and/or "local". Hermes is cloud-only. */
3099
+ compute_modes: string[];
3100
+ /**
3101
+ * Provider family the runtime's OWN credentials can serve ("anthropic" /
3102
+ * "openai"), or "any" for family-agnostic runtimes. Own-route restriction
3103
+ * only — see cross_family_via_parall.
3104
+ */
3105
+ native_model_family: string;
3106
+ /** On the Parall proxy route any catalog model is allowed regardless of family. */
3107
+ cross_family_via_parall: boolean;
3108
+ /** Whether a pinned model is delivered when the agent runs on its own credentials. */
3109
+ own_route_model_pin: boolean;
3110
+ /** Catalog default the UI pre-selects for this runtime. */
3111
+ recommended_model: string;
3112
+ }
3113
+
3114
+ /**
3115
+ * Catalog entry from GET /api/v1/models (public, no auth) — the DB-backed
3116
+ * platform model catalog. Server SSOT: handler.PlatformModelDTO
3117
+ * (docs/engineering-design/model-catalog-design.md §6.1). Replaces the
3118
+ * compile-time PLATFORM_MODELS constant as the picker data source.
3119
+ */
3120
+ export interface PlatformModelInfo {
3121
+ id: string;
3122
+ name: string;
3123
+ /** Provider family ("anthropic", "openai", "google", ...). */
3124
+ family: string;
3125
+ contextWindow: number;
3126
+ maxTokens: number;
3127
+ effortLevels?: string[];
3128
+ /** Runtime type → runtime-native CLI model name. */
3129
+ runtime_names?: Record<string, string>;
3130
+ /** Set on client-behavior variants (the "(1M)" entries) — see isPlatformModelSelectableForRuntime. */
3131
+ upstream_id?: string;
3132
+ /** Display hints: "recommended" / "new" / "beta". */
3133
+ tags?: string[];
3134
+ sort_order: number;
3135
+ /** True only for a legacy entry appended via include_model (not in the active catalog). */
3136
+ hidden?: boolean;
3137
+ }
3138
+
3139
+ export interface PlatformModelsResponse {
3140
+ data: PlatformModelInfo[];
3141
+ /** Catalog content hash — changes whenever the catalog changes. */
3142
+ version: string;
3143
+ }
3144
+
2846
3145
  export interface MachinePreset {
2847
3146
  key: string;
2848
3147
  cpu_cores: number;
@@ -2914,7 +3213,11 @@ export interface BillingInsufficientEvent {
2914
3213
  // Clip Types
2915
3214
  // ============================================================
2916
3215
 
2917
- export type ClipSourceType = 'registry' | 'custom' | 'builtin';
3216
+ // 'channel' is a platform-managed source: it appears on read (Clip.source_type)
3217
+ // but the server rejects it on create, so CreateClipRequest uses the narrower
3218
+ // UserClipSourceType.
3219
+ export type ClipSourceType = 'registry' | 'custom' | 'builtin' | 'channel';
3220
+ export type UserClipSourceType = 'registry' | 'custom' | 'builtin';
2918
3221
  export type ClipStatus = 'active' | 'disabled';
2919
3222
 
2920
3223
  export interface Clip {
@@ -2949,7 +3252,7 @@ export interface CreateClipRequest {
2949
3252
  display_name?: string;
2950
3253
  description?: string;
2951
3254
  manifest?: Record<string, unknown>;
2952
- source_type?: ClipSourceType;
3255
+ source_type?: UserClipSourceType;
2953
3256
  source_ref?: string;
2954
3257
  version?: string;
2955
3258
  machine_id: string;
@@ -3017,6 +3320,15 @@ export type BrowserProfileStatus = 'pending' | 'running' | 'stopped' | 'error' |
3017
3320
  */
3018
3321
  export type BrowserPlacement = 'byoc' | 'hosted';
3019
3322
 
3323
+ /** Deployment-wide hosted browser runtime availability (the controller startup
3324
+ * gate). Drives whether the create UI offers the platform-hosted placement; it
3325
+ * is org-scoped only for auth — the value itself is the same for every org.
3326
+ * Fail-closed: the server reports `available: false` if it cannot verify. */
3327
+ export interface BrowserRuntimeStatus {
3328
+ available: boolean;
3329
+ reason?: string;
3330
+ }
3331
+
3020
3332
  export interface BrowserProfile {
3021
3333
  id: string;
3022
3334
  org_id: string;
@@ -3091,6 +3403,12 @@ export interface MachineBrowserProfile {
3091
3403
  proxy_server?: string | null;
3092
3404
  proxy_username?: string | null;
3093
3405
  proxy_password?: string | null;
3406
+ /** Per-profile fencing tokens (PR #1650): lifecycle_generation bumps on every
3407
+ * open/stop/reset (daemon reports under it; stale reports rejected);
3408
+ * reset_generation bumps only on reset (daemon wipes local state on reconnect
3409
+ * when this is newer than what it last applied). Default 0. */
3410
+ lifecycle_generation: number;
3411
+ reset_generation: number;
3094
3412
  }
3095
3413
 
3096
3414
  export interface BrowserProfileConsent {
@@ -3240,10 +3558,18 @@ export type SearchParams = {
3240
3558
  types?: string;
3241
3559
  chat_id?: string;
3242
3560
  limit?: number;
3561
+ /** Page offset into the per-type candidate pool (default 0). Reachability is capped — past the pool, results are empty with `has_more` false. */
3562
+ offset?: number;
3243
3563
  /** Wiki document-type facet (frontmatter `type`) — a separate axis from `types`. */
3244
3564
  wiki_type?: string;
3245
- /** RFC3339 or YYYY-MM-DD; narrows messages (created_at) and tasks (updated_at). Not applied to wiki. */
3565
+ /** RFC3339 or YYYY-MM-DD lower bound; narrows messages (created_at) and tasks (updated_at). Not applied to wiki. */
3246
3566
  since?: string;
3567
+ /** Sender filter (Slack `from:`) — user id. Messages only; tasks/wiki carry no sender. */
3568
+ from?: string;
3569
+ /** Result order: `relevance` (default, RRF hybrid) or `recent` (newest-first). Recent applies to messages/tasks; wiki stays relevance (no authored timestamp). */
3570
+ order?: 'relevance' | 'recent';
3571
+ /** Collapse cross-chat message results to one hit per chat (default true). Pass false on the full results page to page every match. No effect when `chat_id` is set. */
3572
+ collapse_by_chat?: boolean;
3247
3573
  };
3248
3574
 
3249
3575
  export interface SearchMessageResult {
@@ -3285,4 +3611,6 @@ export interface SearchResponse {
3285
3611
  messages: SearchMessageResult[];
3286
3612
  tasks: SearchTaskResult[];
3287
3613
  wiki: SearchWikiResult[];
3614
+ /** Per-type flag: more results remain past the returned page (within the reachable pool). Lets a tabbed results UI page each type independently. Optional for resilience to older payloads; clients default to all-false. */
3615
+ has_more?: { messages: boolean; tasks: boolean; wiki: boolean };
3288
3616
  }