@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/client.ts CHANGED
@@ -41,6 +41,7 @@ import type {
41
41
  MachineRuntimeAuthState,
42
42
  AgentWorkspaceState,
43
43
  AgentWorkspaceReportStatus,
44
+ DetectedRuntime,
44
45
  BrowseMachineFilesystemResponse,
45
46
  FilesystemEntry,
46
47
  DaemonAgentConfig,
@@ -74,6 +75,14 @@ import type {
74
75
  CreateExternalConnectionInput,
75
76
  UpdateExternalConnectionInput,
76
77
  ExternalConnectionFilters,
78
+ ChannelConnection,
79
+ ChannelConversation,
80
+ ChannelMessage,
81
+ ChannelCredentialsInput,
82
+ ChannelProvisioningSession,
83
+ CreateChannelConnectionInput,
84
+ InitiateChannelProvisioningInput,
85
+ UpdateChannelConnectionInput,
77
86
  ExternalIngressEvent,
78
87
  ExternalIngressEventFilters,
79
88
  ExternalTrigger,
@@ -97,6 +106,8 @@ import type {
97
106
  PlatformConfigResponse,
98
107
  Wiki,
99
108
  WikiBlob,
109
+ CopyWikiFileRequest,
110
+ CopyWikiFileResponse,
100
111
  WikiChangeset,
101
112
  WikiFileUploadResponse,
102
113
  WikiFilePreviewUrlResponse,
@@ -163,6 +174,8 @@ import type {
163
174
  UpdateAutoReloadRequest,
164
175
  ComputePricing,
165
176
  ComputePricingResponse,
177
+ RuntimeCapability,
178
+ PlatformModelsResponse,
166
179
  Clip,
167
180
  AgentClip,
168
181
  CreateClipRequest,
@@ -178,6 +191,7 @@ import type {
178
191
  MachineBrowserProfile,
179
192
  BrowserProfileListItem,
180
193
  BrowserProfileStatus,
194
+ BrowserRuntimeStatus,
181
195
  BrowserProfileConsent,
182
196
  CreateBrowserProfileRequest,
183
197
  UpdateBrowserProfileRequest,
@@ -631,6 +645,17 @@ export class ParallClient {
631
645
  return res.data;
632
646
  }
633
647
 
648
+ /** User IDs of soft-removed (former) org members — for marking their avatar /
649
+ * name as "left" where dormant relations still surface them (DMs, task
650
+ * assignees, message history). */
651
+ async getFormerMemberIds(orgId: string): Promise<string[]> {
652
+ const res = await this.request<{ user_ids: string[] }>(
653
+ 'GET',
654
+ ENDPOINTS.ORG_MEMBERS_FORMER(orgId),
655
+ );
656
+ return res.user_ids ?? [];
657
+ }
658
+
634
659
  async getTeams(orgId: string): Promise<Team[]> {
635
660
  const res = await this.request<{ data: Team[] }>('GET', ENDPOINTS.TEAMS(orgId));
636
661
  return res.data;
@@ -1515,29 +1540,40 @@ export class ParallClient {
1515
1540
  profileId: string,
1516
1541
  status: BrowserProfileStatus,
1517
1542
  errorMsg?: string,
1543
+ // The lifecycle generation this report was issued under (captured at op start,
1544
+ // PR #1650). Omitted by pre-generation callers; the server then falls back to
1545
+ // safe-state compatibility. The server no-ops a report older than the row's.
1546
+ generation?: number,
1518
1547
  ): Promise<void> {
1519
1548
  await this.request('POST', ENDPOINTS.MACHINES_ME_BROWSER_PROFILE_STATUS(profileId), {
1520
1549
  status,
1521
1550
  ...(errorMsg ? { error_msg: errorMsg } : {}),
1551
+ ...(generation !== undefined ? { generation } : {}),
1522
1552
  });
1523
1553
  }
1524
1554
 
1525
1555
  /**
1526
1556
  * `POST /machines/me/health` — bump the Machine's `updated_at` to now and
1527
- * report daemon state. The daemon should call this on a fixed cadence (e.g.
1528
- * every 30s) so an external observer can detect a wedged supervisor. Both
1557
+ * report daemon state. The daemon calls this on startup and again whenever
1558
+ * periodic runtime re-detection produces a CHANGED result (there is no
1559
+ * fixed-cadence keepalive loop today — steady state posts nothing). All
1529
1560
  * fields are optional and only persisted when changed:
1530
1561
  * - `daemonVersion` — the running bundle/launcher version.
1531
1562
  * - `selfUpdateCapable` — whether the daemon can act on a machine.update
1532
1563
  * signal (true under a service manager, false for bare `npx` foreground).
1564
+ * - `detectedRuntimes` — runtime CLIs found on the host. Omitted = no
1565
+ * report this beat (server keeps the stored value); [] = detection ran
1566
+ * and found nothing (server clears to empty).
1533
1567
  */
1534
1568
  async postMachineHeartbeat(opts?: {
1535
1569
  daemonVersion?: string;
1536
1570
  selfUpdateCapable?: boolean;
1571
+ detectedRuntimes?: DetectedRuntime[];
1537
1572
  }): Promise<void> {
1538
1573
  const body: Record<string, unknown> = {};
1539
1574
  if (opts?.daemonVersion) body.daemon_version = opts.daemonVersion;
1540
1575
  if (opts?.selfUpdateCapable !== undefined) body.self_update_capable = opts.selfUpdateCapable;
1576
+ if (opts?.detectedRuntimes !== undefined) body.detected_runtimes = opts.detectedRuntimes;
1541
1577
  return this.request(
1542
1578
  'POST',
1543
1579
  ENDPOINTS.MACHINES_ME_HEALTH,
@@ -2165,6 +2201,124 @@ export class ParallClient {
2165
2201
  return this.request('DELETE', ENDPOINTS.EXTERNAL_CONNECTION(orgId, connectionId));
2166
2202
  }
2167
2203
 
2204
+ // ---- External IM channel (org-scoped) ----
2205
+
2206
+ async createChannelConnection(
2207
+ orgId: string,
2208
+ input: CreateChannelConnectionInput,
2209
+ ): Promise<ChannelConnection> {
2210
+ return this.request('POST', ENDPOINTS.CHANNEL_CONNECTIONS(orgId), input);
2211
+ }
2212
+
2213
+ async listChannelConnections(orgId: string): Promise<{ connections: ChannelConnection[] }> {
2214
+ return this.request('GET', ENDPOINTS.CHANNEL_CONNECTIONS(orgId));
2215
+ }
2216
+
2217
+ async getChannelConnection(orgId: string, connectionId: string): Promise<ChannelConnection> {
2218
+ return this.request('GET', ENDPOINTS.CHANNEL_CONNECTION(orgId, connectionId));
2219
+ }
2220
+
2221
+ async updateChannelConnection(
2222
+ orgId: string,
2223
+ connectionId: string,
2224
+ patch: UpdateChannelConnectionInput,
2225
+ ): Promise<ChannelConnection> {
2226
+ return this.request('PATCH', ENDPOINTS.CHANNEL_CONNECTION(orgId, connectionId), patch);
2227
+ }
2228
+
2229
+ async archiveChannelConnection(orgId: string, connectionId: string): Promise<ChannelConnection> {
2230
+ return this.request('DELETE', ENDPOINTS.CHANNEL_CONNECTION(orgId, connectionId));
2231
+ }
2232
+
2233
+ async deliverChannelCredentials(
2234
+ orgId: string,
2235
+ connectionId: string,
2236
+ credentials: ChannelCredentialsInput,
2237
+ ): Promise<ChannelConnection> {
2238
+ return this.request(
2239
+ 'POST',
2240
+ ENDPOINTS.CHANNEL_CONNECTION_CREDENTIALS(orgId, connectionId),
2241
+ credentials,
2242
+ );
2243
+ }
2244
+
2245
+ async regenerateChannelIngressToken(
2246
+ orgId: string,
2247
+ connectionId: string,
2248
+ ): Promise<ChannelConnection> {
2249
+ return this.request(
2250
+ 'POST',
2251
+ ENDPOINTS.CHANNEL_CONNECTION_INGRESS_TOKEN_REGENERATE(orgId, connectionId),
2252
+ );
2253
+ }
2254
+
2255
+ /** Start a Feishu one-click provisioning session (device flow QR). */
2256
+ async initiateChannelProvisioning(
2257
+ orgId: string,
2258
+ input: InitiateChannelProvisioningInput,
2259
+ ): Promise<ChannelProvisioningSession> {
2260
+ return this.request('POST', ENDPOINTS.CHANNEL_PROVISIONING(orgId), input);
2261
+ }
2262
+
2263
+ /**
2264
+ * Lazy status poll — server-side this may forward one provider poll, so
2265
+ * call it at the session's `poll_interval_seconds` cadence, not faster.
2266
+ */
2267
+ async getChannelProvisioningSession(
2268
+ orgId: string,
2269
+ sessionId: string,
2270
+ ): Promise<ChannelProvisioningSession> {
2271
+ return this.request('GET', ENDPOINTS.CHANNEL_PROVISIONING_SESSION(orgId, sessionId));
2272
+ }
2273
+
2274
+ async cancelChannelProvisioning(
2275
+ orgId: string,
2276
+ sessionId: string,
2277
+ ): Promise<ChannelProvisioningSession> {
2278
+ return this.request('POST', ENDPOINTS.CHANNEL_PROVISIONING_CANCEL(orgId, sessionId));
2279
+ }
2280
+
2281
+ async listChannelConversations(
2282
+ orgId: string,
2283
+ connectionId: string,
2284
+ ): Promise<{ conversations: ChannelConversation[] }> {
2285
+ return this.request('GET', ENDPOINTS.CHANNEL_CONNECTION_CONVERSATIONS(orgId, connectionId));
2286
+ }
2287
+
2288
+ async getChannelConversation(
2289
+ orgId: string,
2290
+ conversationId: string,
2291
+ ): Promise<ChannelConversation> {
2292
+ return this.request('GET', ENDPOINTS.CHANNEL_CONVERSATION(orgId, conversationId));
2293
+ }
2294
+
2295
+ async listChannelConversationMessages(
2296
+ orgId: string,
2297
+ conversationId: string,
2298
+ limit?: number,
2299
+ ): Promise<{ messages: ChannelMessage[] }> {
2300
+ return this.request(
2301
+ 'GET',
2302
+ ENDPOINTS.CHANNEL_CONVERSATION_MESSAGES(orgId, conversationId),
2303
+ undefined,
2304
+ { limit },
2305
+ );
2306
+ }
2307
+
2308
+ async setChannelConversationSession(
2309
+ orgId: string,
2310
+ conversationId: string,
2311
+ agentSessionId: string,
2312
+ ): Promise<ChannelConversation> {
2313
+ return this.request('PATCH', ENDPOINTS.CHANNEL_CONVERSATION_SESSION(orgId, conversationId), {
2314
+ agent_session_id: agentSessionId,
2315
+ });
2316
+ }
2317
+
2318
+ async getChannelMessage(orgId: string, messageId: string): Promise<ChannelMessage> {
2319
+ return this.request('GET', ENDPOINTS.CHANNEL_MESSAGE(orgId, messageId));
2320
+ }
2321
+
2168
2322
  async getExternalTriggerSchema(
2169
2323
  orgId: string,
2170
2324
  connectionId: string,
@@ -2254,6 +2408,26 @@ export class ParallClient {
2254
2408
  return this.request('GET', ENDPOINTS.WIKI(orgId, wikiId));
2255
2409
  }
2256
2410
 
2411
+ /** Soft-delete an entire wiki (org owner only; the default wiki is
2412
+ * protected → 403 WIKI_PROTECTED). Returns the deleted wiki. Recoverable
2413
+ * via restoreWiki within the retention window. */
2414
+ async deleteWiki(orgId: string, wikiId: string): Promise<Wiki> {
2415
+ return this.request('DELETE', ENDPOINTS.WIKI(orgId, wikiId));
2416
+ }
2417
+
2418
+ /** Restore a soft-deleted wiki (org owner only). 409 WIKI_PURGE_STARTED once
2419
+ * purge has been claimed (no longer restorable), or 404 once the row is gone.
2420
+ * Returns the restored wiki. */
2421
+ async restoreWiki(orgId: string, wikiId: string): Promise<Wiki> {
2422
+ return this.request('POST', ENDPOINTS.WIKI_RESTORE(orgId, wikiId));
2423
+ }
2424
+
2425
+ /** Recycle bin — list soft-deleted wikis for the org (owner only). */
2426
+ async getDeletedWikis(orgId: string): Promise<Wiki[]> {
2427
+ const res = await this.request<{ data: Wiki[] }>('GET', ENDPOINTS.WIKIS_DELETED(orgId));
2428
+ return res.data;
2429
+ }
2430
+
2257
2431
  async getWikiTree(
2258
2432
  orgId: string,
2259
2433
  wikiId: string,
@@ -2301,6 +2475,20 @@ export class ParallClient {
2301
2475
  return blob;
2302
2476
  }
2303
2477
 
2478
+ /**
2479
+ * Copy one wiki file to another path as an independent snapshot (no sync).
2480
+ * Used to "share a private file out": copy a file from the caller's personal
2481
+ * namespace (`users/{userId}/…`) into the public wiki, leaving the original
2482
+ * untouched. 409 `FILE_EXISTS` if `dest_path` already exists.
2483
+ */
2484
+ async copyWikiFile(
2485
+ orgId: string,
2486
+ wikiId: string,
2487
+ req: CopyWikiFileRequest,
2488
+ ): Promise<CopyWikiFileResponse> {
2489
+ return this.request('POST', ENDPOINTS.WIKI_COPY(orgId, wikiId), req);
2490
+ }
2491
+
2304
2492
  async getWikiNodeSections(
2305
2493
  orgId: string,
2306
2494
  wikiId: string,
@@ -2828,6 +3016,21 @@ export class ParallClient {
2828
3016
  return resp.data;
2829
3017
  }
2830
3018
 
3019
+ /** Runtime capability table (public) — SSOT for the create/settings interlock. */
3020
+ async getRuntimes(): Promise<RuntimeCapability[]> {
3021
+ const resp = await this.request<{ data: RuntimeCapability[] }>('GET', ENDPOINTS.RUNTIMES());
3022
+ return resp.data;
3023
+ }
3024
+
3025
+ /**
3026
+ * Platform model catalog (public) — DB-backed, replaces the compile-time
3027
+ * PLATFORM_MODELS constant. Pass includeModel to keep an agent's pinned
3028
+ * hidden legacy model representable (settings picker).
3029
+ */
3030
+ async getModels(includeModel?: string): Promise<PlatformModelsResponse> {
3031
+ return this.request<PlatformModelsResponse>('GET', ENDPOINTS.MODELS(includeModel));
3032
+ }
3033
+
2831
3034
  // ---- Clips ----
2832
3035
 
2833
3036
  async listClips(orgId: string): Promise<Clip[]> {
@@ -2895,6 +3098,12 @@ export class ParallClient {
2895
3098
  return resp.data;
2896
3099
  }
2897
3100
 
3101
+ /** Deployment-wide hosted browser runtime availability — drives whether the
3102
+ * create UI offers the platform-hosted placement. Fail-closed server-side. */
3103
+ async getBrowserRuntimeStatus(orgId: string): Promise<BrowserRuntimeStatus> {
3104
+ return this.request('GET', ENDPOINTS.BROWSER_RUNTIME_STATUS(orgId));
3105
+ }
3106
+
2898
3107
  /** Org-wide browser-profile discovery list. Returns the sanitized
2899
3108
  * {@link BrowserProfileListItem} shape (not the full domain model), each row
2900
3109
  * carrying a per-viewer `can_open` control hint. */
package/src/constants.ts CHANGED
@@ -7,11 +7,17 @@ export const WIKI_BASE = '/wiki/v1';
7
7
  // Clip-service base path (served by clip-service, routed via LB path rules)
8
8
  export const CLIP_BASE = '/clip/v1';
9
9
 
10
- // LLM model catalog — mirrors server/pkg/llmproxy/models.go DefaultModels.
11
- // SSOT is the Go side; keep this list in sync when models change. `provider`
12
- // is used by clients to filter runtime-specific pickers. `runtime_names` is
13
- // included for catalog completeness and agent runtime delivery; product UI
14
- // should continue to display canonical model IDs/names.
10
+ // LLM model catalog — frozen compile-time mirror of the catalog at the time
11
+ // the DB-backed source shipped (docs/engineering-design/model-catalog-design.md).
12
+ // Kept ONLY as the offline/degraded fallback for pickers; it is NOT updated
13
+ // when models are added through the admin catalog.
14
+ /**
15
+ * @deprecated The catalog is DB-backed — fetch it via `client.getModels()`
16
+ * (GET /api/v1/models). This constant is a frozen degraded-mode fallback and
17
+ * will be removed after one release cycle. Still structurally consumed by
18
+ * FALLBACK_CATALOG (`@parall/app` agent-interlock) — do not delete before
19
+ * that fallback strategy changes (PR-3).
20
+ */
15
21
  export const PLATFORM_MODELS = [
16
22
  // Fable 5 rejects an explicit thinking "disabled" param (400) — bridges must
17
23
  // omit the thinking config entirely instead of disabling it.
@@ -211,6 +217,12 @@ export const PLATFORM_MODELS = [
211
217
 
212
218
  // Hidden from new-agent pickers, but available to editors so persisted Beta
213
219
  // agent defaults remain representable.
220
+ /**
221
+ * @deprecated Fetch via `client.getModels(currentModelId)` — the include_model
222
+ * parameter serves the legacy-pin case. Frozen degraded-mode fallback only.
223
+ * Still structurally consumed by FALLBACK_CATALOG (`@parall/app`
224
+ * agent-interlock) — do not delete before that fallback changes (PR-3).
225
+ */
214
226
  export const PLATFORM_LEGACY_MODELS = [
215
227
  {
216
228
  id: 'openai/gpt-5.4-mini',
@@ -284,9 +296,13 @@ export const PLATFORM_LEGACY_MODELS = [
284
296
  },
285
297
  ] as const;
286
298
 
287
- // Mirrors server/pkg/llmproxy/models.go DefaultModelIDForRuntime. Go remains
288
- // the SSOT; keep this client-side fallback in sync until the catalog is codegen
289
- // or fetched before render.
299
+ // Frozen mirror of the per-runtime defaults at ship time of the DB-backed
300
+ // catalog (llm_runtime_defaults). Live values arrive as recommended_model on
301
+ // GET /runtimes.
302
+ /**
303
+ * @deprecated Read `recommended_model` from `client.getRuntimes()` instead.
304
+ * Frozen degraded-mode fallback only.
305
+ */
290
306
  export const PLATFORM_DEFAULT_MODEL_BY_RUNTIME = {
291
307
  openclaw: 'anthropic/claude-sonnet-4.6',
292
308
  'claude-code': 'anthropic/claude-sonnet-4.6',
@@ -312,6 +328,11 @@ export function platformDefaultModelForRuntime(runtime: string | null | undefine
312
328
  case '':
313
329
  case 'openclaw':
314
330
  case 'claude-code':
331
+ // hermes / parel route through the Parall proxy and accept any model; the
332
+ // server (DefaultModelIDForRuntime) recommends the platform default, so
333
+ // mirror it.
334
+ case 'hermes':
335
+ case 'parel':
315
336
  return PLATFORM_DEFAULT_MODEL_BY_RUNTIME.openclaw;
316
337
  case 'codex':
317
338
  return PLATFORM_DEFAULT_MODEL_BY_RUNTIME.codex;
@@ -347,6 +368,7 @@ export const ENDPOINTS = {
347
368
  // Org-scoped
348
369
  ORG: (orgId: string) => `${API_BASE}/orgs/${orgId}`,
349
370
  ORG_MEMBERS: (orgId: string) => `${API_BASE}/orgs/${orgId}/members`,
371
+ ORG_MEMBERS_FORMER: (orgId: string) => `${API_BASE}/orgs/${orgId}/members/former`,
350
372
  TEAMS: (orgId: string) => `${API_BASE}/orgs/${orgId}/teams`,
351
373
  ORG_MEMBERS_ONLINE: (orgId: string) => `${API_BASE}/orgs/${orgId}/members/online`,
352
374
  ORG_MEMBER: (orgId: string, userId: string) => `${API_BASE}/orgs/${orgId}/members/${userId}`,
@@ -584,6 +606,30 @@ export const ENDPOINTS = {
584
606
  EXTERNAL_TRIGGER_RUN: (orgId: string, runId: string) =>
585
607
  `${API_BASE}/orgs/${orgId}/external-trigger-runs/${runId}`,
586
608
 
609
+ // External IM channel (org-scoped, platform-mediated Feishu/Slack)
610
+ CHANNEL_CONNECTIONS: (orgId: string) => `${API_BASE}/orgs/${orgId}/channel-connections`,
611
+ CHANNEL_CONNECTION: (orgId: string, connectionId: string) =>
612
+ `${API_BASE}/orgs/${orgId}/channel-connections/${connectionId}`,
613
+ CHANNEL_CONNECTION_CREDENTIALS: (orgId: string, connectionId: string) =>
614
+ `${API_BASE}/orgs/${orgId}/channel-connections/${connectionId}/credentials`,
615
+ CHANNEL_CONNECTION_INGRESS_TOKEN_REGENERATE: (orgId: string, connectionId: string) =>
616
+ `${API_BASE}/orgs/${orgId}/channel-connections/${connectionId}/ingress-token/regenerate`,
617
+ CHANNEL_CONNECTION_CONVERSATIONS: (orgId: string, connectionId: string) =>
618
+ `${API_BASE}/orgs/${orgId}/channel-connections/${connectionId}/conversations`,
619
+ CHANNEL_CONVERSATION: (orgId: string, conversationId: string) =>
620
+ `${API_BASE}/orgs/${orgId}/channel-conversations/${conversationId}`,
621
+ CHANNEL_CONVERSATION_MESSAGES: (orgId: string, conversationId: string) =>
622
+ `${API_BASE}/orgs/${orgId}/channel-conversations/${conversationId}/messages`,
623
+ CHANNEL_CONVERSATION_SESSION: (orgId: string, conversationId: string) =>
624
+ `${API_BASE}/orgs/${orgId}/channel-conversations/${conversationId}/session`,
625
+ CHANNEL_MESSAGE: (orgId: string, messageId: string) =>
626
+ `${API_BASE}/orgs/${orgId}/channel-messages/${messageId}`,
627
+ CHANNEL_PROVISIONING: (orgId: string) => `${API_BASE}/orgs/${orgId}/channel-provisioning`,
628
+ CHANNEL_PROVISIONING_SESSION: (orgId: string, sessionId: string) =>
629
+ `${API_BASE}/orgs/${orgId}/channel-provisioning/${sessionId}`,
630
+ CHANNEL_PROVISIONING_CANCEL: (orgId: string, sessionId: string) =>
631
+ `${API_BASE}/orgs/${orgId}/channel-provisioning/${sessionId}/cancel`,
632
+
587
633
  // Invitations (org-scoped, admin)
588
634
  ORG_INVITATIONS: (orgId: string) => `${API_BASE}/orgs/${orgId}/invitations`,
589
635
  ORG_INVITATION: (orgId: string, invId: string) =>
@@ -608,9 +654,17 @@ export const ENDPOINTS = {
608
654
 
609
655
  // Wikis (org-scoped, served by wiki-service)
610
656
  WIKIS: (orgId: string) => `${WIKI_BASE}/orgs/${orgId}/wikis`,
657
+ // Recycle bin — soft-deleted wikis (owner only). Must precede WIKI in the
658
+ // backend router so `deleted` isn't captured as a {wikiId}; the SDK builder
659
+ // is just a string.
660
+ WIKIS_DELETED: (orgId: string) => `${WIKI_BASE}/orgs/${orgId}/wikis/deleted`,
661
+ // Detail URL — also reused for DELETE (soft-delete) and `${WIKI}/restore`.
611
662
  WIKI: (orgId: string, wikiId: string) => `${WIKI_BASE}/orgs/${orgId}/wikis/${wikiId}`,
663
+ WIKI_RESTORE: (orgId: string, wikiId: string) =>
664
+ `${WIKI_BASE}/orgs/${orgId}/wikis/${wikiId}/restore`,
612
665
  WIKI_TREE: (orgId: string, wikiId: string) => `${WIKI_BASE}/orgs/${orgId}/wikis/${wikiId}/tree`,
613
666
  WIKI_BLOB: (orgId: string, wikiId: string) => `${WIKI_BASE}/orgs/${orgId}/wikis/${wikiId}/blob`,
667
+ WIKI_COPY: (orgId: string, wikiId: string) => `${WIKI_BASE}/orgs/${orgId}/wikis/${wikiId}/copy`,
614
668
  WIKI_NODE_SECTIONS: (orgId: string, wikiId: string) =>
615
669
  `${WIKI_BASE}/orgs/${orgId}/wikis/${wikiId}/node-sections`,
616
670
  WIKI_SEARCH: (orgId: string, wikiId: string) =>
@@ -734,6 +788,19 @@ export const ENDPOINTS = {
734
788
  BILLING_SETUP_INTENT: (orgId: string) => `${API_BASE}/orgs/${orgId}/billing/setup-intent`,
735
789
  COMPUTE_PRICING: () => `${API_BASE}/billing/compute-pricing`,
736
790
 
791
+ // Runtime capability table (public, no auth) — SSOT for the create/settings
792
+ // interlock: per-runtime compute modes, native model family, cross-family
793
+ // availability, and recommended model.
794
+ RUNTIMES: () => `${API_BASE}/runtimes`,
795
+
796
+ // Model catalog (public, no auth) — served from the server's DB-backed
797
+ // snapshot. Optional include_model keeps an agent's pinned hidden legacy
798
+ // model representable in settings pickers.
799
+ MODELS: (includeModel?: string) =>
800
+ includeModel
801
+ ? `${API_BASE}/models?include_model=${encodeURIComponent(includeModel)}`
802
+ : `${API_BASE}/models`,
803
+
737
804
  // ADMIN_GRANTS intentionally NOT exported here — it sits under the
738
805
  // unauthenticated `/internal/admin/*` surface and must not bleed into the
739
806
  // public SDK. Admin dashboard hits the URL directly from its own client.
@@ -750,6 +817,7 @@ export const ENDPOINTS = {
750
817
  `${CLIP_BASE}/orgs/${orgId}/agents/${agentId}/clips/${clipId}`,
751
818
  CLIP_INVOKE: (orgId: string) => `${CLIP_BASE}/orgs/${orgId}/invoke`,
752
819
  CLIP_ONLINE: (orgId: string) => `${CLIP_BASE}/orgs/${orgId}/online`,
820
+ BROWSER_RUNTIME_STATUS: (orgId: string) => `${CLIP_BASE}/orgs/${orgId}/browser-runtime/status`,
753
821
  BROWSER_PROFILES: (orgId: string) => `${CLIP_BASE}/orgs/${orgId}/browser-profiles`,
754
822
  BROWSER_PROFILE: (orgId: string, profileId: string) =>
755
823
  `${CLIP_BASE}/orgs/${orgId}/browser-profiles/${profileId}`,
@@ -772,6 +840,42 @@ export const ENDPOINTS = {
772
840
  CLIP_REGISTRY: () => `${CLIP_BASE}/registry/clips`,
773
841
  } as const;
774
842
 
843
+ /**
844
+ * Wiki personal-namespace prefix — the path subtree a member owns for
845
+ * self-service private spaces: `users/{userId}/`. SSOT mirror of the
846
+ * wiki-service `personalNamespacePrefix` (server/internal/wiki/handler/acl.go):
847
+ * a member may create/manage path restrictions anywhere under this prefix
848
+ * without org/path admin. The trailing slash is load-bearing — it keeps a user
849
+ * ID that is a string prefix of another from matching across namespaces.
850
+ */
851
+ export function wikiPersonalNamespacePrefix(userId: string): string {
852
+ return `users/${userId}/`;
853
+ }
854
+
855
+ /**
856
+ * Whether `path` falls inside `userId`'s personal namespace (the namespace root
857
+ * itself or any descendant). Mirrors the server's `ownsPersonalNamespacePath`;
858
+ * use it for UI affordance gating only — the wiki-service stays the security
859
+ * SSOT and re-checks on every mutation.
860
+ */
861
+ export function isInWikiPersonalNamespace(userId: string, path: string): boolean {
862
+ if (!userId) return false;
863
+ const prefix = wikiPersonalNamespacePrefix(userId);
864
+ return path === prefix || path.startsWith(prefix);
865
+ }
866
+
867
+ /**
868
+ * Whether `path` falls inside ANY user's personal namespace (`users/{id}/…`),
869
+ * regardless of whose. UI affordance helper — e.g. badging member personal-space
870
+ * restriction rows in the admin Access tab, or keeping "copy to shared wiki"
871
+ * destinations out of personal namespaces. Matches by shape (`users/<segment>/`
872
+ * or the bare `users/<segment>`), deliberately broader than
873
+ * `isInWikiPersonalNamespace`.
874
+ */
875
+ export function isWikiPersonalNamespacePath(path: string): boolean {
876
+ return /^users\/[^/]+(\/|$)/.test(path);
877
+ }
878
+
775
879
  // WebSocket event types
776
880
  export const WS_EVENTS = {
777
881
  // Client -> Server
@@ -813,10 +917,13 @@ export const WS_EVENTS = {
813
917
  INVITATION_REVOKED: 'invitation.revoked',
814
918
  ORG_JOIN_REQUEST_NEW: 'org.join_request.new',
815
919
  ORG_INVITE_LINK_JOINED: 'org.invite_link.joined',
920
+ ORG_MEMBER_REMOVED: 'org.member.removed',
816
921
  AGENT_CONFIG_UPDATE: 'agent_config.update',
817
922
  PRESENCE_UPDATE: 'presence.update',
818
923
  WIKI_CHANGESET_CREATED: 'wiki.changeset.created',
819
924
  WIKI_CHANGESET_UPDATED: 'wiki.changeset.updated',
925
+ WIKI_DELETED: 'wiki.deleted',
926
+ WIKI_RESTORED: 'wiki.restored',
820
927
  COMMENT_CREATED: 'comment.created',
821
928
  COMMENT_UPDATED: 'comment.updated',
822
929
  COMMENT_DELETED: 'comment.deleted',
@@ -844,6 +951,7 @@ export const WS_EVENTS = {
844
951
  MACHINE_FILESYSTEM_BROWSE: 'machine.filesystem.browse',
845
952
  MACHINE_UPDATE: 'machine.update',
846
953
  MACHINE_CONFIG_UPDATED: 'machine.config.updated',
954
+ MACHINE_AGENT_CONFIG_UPDATED: 'machine.agent_config.updated',
847
955
  MACHINE_CLIP_SYNC: 'machine.clip.sync',
848
956
  MACHINE_BROWSER_PROFILE_LIFECYCLE: 'machine.browser_profile.lifecycle',
849
957
  MACHINE_BROWSER_PROFILE_VIEWER: 'machine.browser_profile.viewer',