@k2b/cloud 0.6.0 → 0.8.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 (349) hide show
  1. package/README.md +2 -2
  2. package/package.json +22 -11
  3. package/scripts/README.md +10 -0
  4. package/scripts/browser-performance.ts +14 -0
  5. package/scripts/build-pdf-renderer.ts +39 -0
  6. package/scripts/build.ts +20 -35
  7. package/scripts/preload.ts +16 -12
  8. package/scripts/sync-recovery-smoke.ts +22 -6
  9. package/src/_internal/app-presentation.ts +14 -2
  10. package/src/_internal/build-metadata.ts +8 -1
  11. package/src/_internal/capabilities.ts +131 -7
  12. package/src/_internal/capability-streams.ts +81 -0
  13. package/src/_internal/define-app.ts +80 -37
  14. package/src/_internal/heartbeat.ts +4 -0
  15. package/src/_internal/help-catalog.ts +27 -172
  16. package/src/_internal/help.ts +13 -76
  17. package/src/_internal/page-responses.ts +2 -0
  18. package/src/_internal/postgres-application-name.ts +30 -0
  19. package/src/_internal/process-identity.ts +15 -0
  20. package/src/_internal/process-sync.ts +1 -1
  21. package/src/_internal/readiness.ts +4 -1
  22. package/src/_internal/registry-validation.ts +29 -25
  23. package/src/_internal/registry.ts +2 -53
  24. package/src/_internal/runtime-context.ts +5 -16
  25. package/src/_internal/server-timing.ts +13 -0
  26. package/src/_internal/status-preserving-ssr.ts +18 -6
  27. package/src/_internal/web-vitals-asset.ts +10 -0
  28. package/src/access/PermissionEditor.tsx +18 -151
  29. package/src/access/PrincipalPicker.tsx +110 -0
  30. package/src/access/ResourceApiKeys.tsx +1 -1
  31. package/src/access/ui.ts +1 -0
  32. package/src/ai/admin.ts +14 -13
  33. package/src/ai/assistant-models.ts +12 -1
  34. package/src/ai/audio-format.ts +34 -0
  35. package/src/ai/audio-tool.ts +90 -0
  36. package/src/ai/browser-code-contracts.ts +201 -0
  37. package/src/ai/browser.ts +4 -0
  38. package/src/ai/capabilities.ts +55 -36
  39. package/src/ai/capability-execution.ts +47 -12
  40. package/src/ai/chat/blocks.tsx +229 -90
  41. package/src/ai/chat/builtin-tools.tsx +2 -4
  42. package/src/ai/chat/capability-result.ts +15 -0
  43. package/src/ai/chat/capability-table.tsx +2 -10
  44. package/src/ai/chat/composer-adapter.ts +83 -3
  45. package/src/ai/chat/message-actions.tsx +8 -7
  46. package/src/ai/chat/message-utils.ts +25 -1
  47. package/src/ai/chat/messages.ts +2 -0
  48. package/src/ai/chat/presentation.tsx +31 -47
  49. package/src/ai/chat/primitives.tsx +43 -1
  50. package/src/ai/chat/tool-disclosure.tsx +21 -2
  51. package/src/ai/chat/tool-groups.ts +58 -0
  52. package/src/ai/chat-quotas.ts +25 -0
  53. package/src/ai/chat-task-contracts.ts +14 -10
  54. package/src/ai/chat-tasks.ts +297 -43
  55. package/src/ai/client/controller.ts +165 -39
  56. package/src/ai/client/file-source.ts +12 -6
  57. package/src/ai/client/live-connection.ts +1 -0
  58. package/src/ai/client/projection.ts +29 -11
  59. package/src/ai/client/transport.ts +6 -0
  60. package/src/ai/code-capability-routes.ts +105 -0
  61. package/src/ai/code-capability-transport.ts +39 -0
  62. package/src/ai/code-execution.ts +53 -0
  63. package/src/ai/code-mode-skill.ts +118 -0
  64. package/src/ai/code-runtime-tools.ts +179 -0
  65. package/src/ai/code-source-contracts.ts +270 -0
  66. package/src/ai/code-source-tools.ts +188 -0
  67. package/src/ai/compaction.ts +27 -6
  68. package/src/ai/data-analysis-skill.ts +10 -0
  69. package/src/ai/default-tools.ts +99 -3
  70. package/src/ai/dictation-runtime.ts +171 -0
  71. package/src/ai/dictations.ts +188 -0
  72. package/src/ai/draft-content.ts +49 -0
  73. package/src/ai/enrich.ts +6 -0
  74. package/src/ai/executor.ts +189 -33
  75. package/src/ai/fetch-file-tool.ts +2 -2
  76. package/src/ai/file-content-version.ts +27 -0
  77. package/src/ai/file-context.ts +2 -1
  78. package/src/ai/file-media-type.ts +12 -0
  79. package/src/ai/file-reference-contracts.ts +15 -0
  80. package/src/ai/file-tools.ts +8 -1
  81. package/src/ai/files-store.ts +212 -71
  82. package/src/ai/fixtures/vision-encrypted.pdf +0 -0
  83. package/src/ai/grids-skill.ts +42 -10
  84. package/src/ai/http.ts +26 -8
  85. package/src/ai/index.ts +26 -3
  86. package/src/ai/inference-calls.ts +197 -0
  87. package/src/ai/live-events.ts +5 -1
  88. package/src/ai/live.ts +1 -1
  89. package/src/ai/markdown-pdf-tool.ts +2 -2
  90. package/src/ai/memories.ts +30 -27
  91. package/src/ai/memory-learning-runs.ts +1 -1
  92. package/src/ai/memory-learning.ts +64 -32
  93. package/src/ai/memory-tool.ts +1 -5
  94. package/src/ai/memory-workflow-evidence.ts +9 -4
  95. package/src/ai/message-queue.ts +178 -0
  96. package/src/ai/migrate.ts +189 -11
  97. package/src/ai/model-pricing.ts +43 -0
  98. package/src/ai/pdf-render-worker.ts +75 -0
  99. package/src/ai/pdf-render.fixture.ts +28 -0
  100. package/src/ai/pdf-render.ts +77 -0
  101. package/src/ai/projects-routes.ts +18 -43
  102. package/src/ai/projects.ts +33 -14
  103. package/src/ai/protocol.ts +3 -0
  104. package/src/ai/provider.ts +1 -8
  105. package/src/ai/quota-provider.ts +192 -0
  106. package/src/ai/quota-report.ts +152 -0
  107. package/src/ai/quotas-migrate.ts +45 -0
  108. package/src/ai/quotas.ts +168 -0
  109. package/src/ai/routes.ts +241 -11
  110. package/src/ai/run-timeout.ts +12 -0
  111. package/src/ai/runtime-tools.ts +7 -5
  112. package/src/ai/runtime.ts +74 -17
  113. package/src/ai/settings.ts +36 -8
  114. package/src/ai/short-id.ts +4 -5
  115. package/src/ai/skill-catalog.ts +0 -10
  116. package/src/ai/skill-search.ts +58 -0
  117. package/src/ai/skill-seeds.ts +114 -20
  118. package/src/ai/skill-tool.ts +21 -5
  119. package/src/ai/skills-routes.ts +49 -4
  120. package/src/ai/skills.ts +358 -163
  121. package/src/ai/store.ts +416 -201
  122. package/src/ai/stream.ts +5 -0
  123. package/src/ai/structured-runs.ts +4 -3
  124. package/src/ai/structured.ts +12 -1
  125. package/src/ai/system-prompt.ts +14 -2
  126. package/src/ai/task-contracts.ts +84 -0
  127. package/src/ai/task-execution.ts +92 -0
  128. package/src/ai/timeline.ts +2 -5
  129. package/src/ai/todo-contracts.ts +32 -0
  130. package/src/ai/todo-tool.ts +19 -0
  131. package/src/ai/tool-audit.ts +30 -40
  132. package/src/ai/tools.ts +6 -0
  133. package/src/ai/transcription.ts +202 -0
  134. package/src/ai/turn-timing.ts +114 -0
  135. package/src/ai/types.ts +58 -10
  136. package/src/ai/ui.tsx +3 -0
  137. package/src/ai/usage.ts +118 -118
  138. package/src/ai/vision-tool.ts +60 -10
  139. package/src/api/admin-ai-projects.ts +2 -2
  140. package/src/api/admin-ai-quotas.ts +80 -0
  141. package/src/api/admin-ai-skills.ts +48 -5
  142. package/src/api/admin-ai-usage.ts +2 -2
  143. package/src/api/admin-core-settings.ts +25 -0
  144. package/src/api/admin-identity.ts +91 -51
  145. package/src/api/admin-linux-identities.ts +10 -4
  146. package/src/api/admin-rail.ts +58 -0
  147. package/src/api/announcements.ts +14 -0
  148. package/src/api/app-approval.ts +2 -2
  149. package/src/api/auth.ts +1 -1
  150. package/src/api/capabilities.ts +324 -43
  151. package/src/api/capability-streams.ts +262 -0
  152. package/src/api/help.ts +8 -44
  153. package/src/api/index.ts +8 -2
  154. package/src/api/mcp.ts +45 -34
  155. package/src/api/me-web-vitals.ts +35 -0
  156. package/src/api/me.ts +2 -0
  157. package/src/api/search/schemas.ts +26 -15
  158. package/src/api/search.bench.ts +1 -1
  159. package/src/api/search.ts +54 -17
  160. package/src/browser/CloudResourceSearch.tsx +640 -702
  161. package/src/browser/app-approval-vault.ts +19 -7
  162. package/src/browser/command-bridge.ts +109 -0
  163. package/src/browser/command-shortcuts.ts +76 -0
  164. package/src/browser/commands.ts +67 -0
  165. package/src/browser/navigation-search.ts +48 -0
  166. package/src/browser/notifications.ts +1 -1
  167. package/src/browser/resource-picker.tsx +5 -18
  168. package/src/browser/resource-search-dialog.ts +9 -0
  169. package/src/browser/resource-search-input.ts +46 -0
  170. package/src/browser/resource-search-messages.ts +76 -40
  171. package/src/browser/resource-search.ts +7 -0
  172. package/src/browser/search-bridge.ts +102 -0
  173. package/src/browser/search-commands.ts +59 -0
  174. package/src/browser/search.ts +13 -0
  175. package/src/browser/spotlight-position.ts +208 -0
  176. package/src/browser/testing.ts +6 -0
  177. package/src/browser/web-vitals.ts +25 -0
  178. package/src/capabilities/claims.ts +179 -0
  179. package/src/capabilities/client.ts +34 -0
  180. package/src/capabilities/command-link.ts +16 -0
  181. package/src/capabilities/executions.ts +298 -0
  182. package/src/capabilities/migrate.ts +81 -0
  183. package/src/capabilities/server.ts +116 -35
  184. package/src/capabilities/stream-body.ts +16 -0
  185. package/src/capabilities/streams.ts +28 -0
  186. package/src/cli/admin/ai-quotas.ts +161 -0
  187. package/src/cli/admin/ai-skills.ts +64 -0
  188. package/src/cli/admin/ai-usage.ts +53 -30
  189. package/src/cli/admin/app-credentials.ts +77 -0
  190. package/src/cli/admin/data.ts +17 -0
  191. package/src/cli/admin/index.ts +10 -1
  192. package/src/cli/admin/instance.ts +2 -2
  193. package/src/cli/admin/linux.ts +4 -2
  194. package/src/cli/admin/sync.ts +1 -1
  195. package/src/cli/capabilities.ts +42 -0
  196. package/src/cli/capability-streams.ts +25 -0
  197. package/src/cli/index.ts +3 -2
  198. package/src/config/define-env.ts +97 -0
  199. package/src/config/env.ts +147 -51
  200. package/src/config/index.ts +3 -1
  201. package/src/contracts/app.ts +23 -3
  202. package/src/contracts/capabilities.ts +75 -5
  203. package/src/contracts/capability-streams.ts +46 -0
  204. package/src/contracts/commands.ts +49 -0
  205. package/src/contracts/index.ts +5 -2
  206. package/src/contracts/rail-admin.ts +38 -0
  207. package/src/contracts/rail-preferences.ts +16 -0
  208. package/src/contracts/registry.ts +5 -27
  209. package/src/contracts/settings-types.ts +1 -1
  210. package/src/contracts/shared.ts +18 -5
  211. package/src/contracts/web-vitals.ts +27 -0
  212. package/src/desktop/index.ts +1 -0
  213. package/src/desktop/solid.tsx +1 -1
  214. package/src/index.ts +8 -6
  215. package/src/server/index.ts +3 -0
  216. package/src/server/middleware/auth.ts +3 -2
  217. package/src/server/middleware/route-template.ts +9 -3
  218. package/src/server/middleware/runtime.ts +2 -1
  219. package/src/server/middleware/settings.ts +17 -11
  220. package/src/server/ratelimit.ts +1 -1
  221. package/src/server/services/access-revision.ts +10 -0
  222. package/src/server/services/access.ts +3 -1
  223. package/src/server/services/index.ts +1 -0
  224. package/src/services/account-category-policy.ts +9 -0
  225. package/src/services/account-lifecycle/audit.ts +1 -1
  226. package/src/services/accounts/app.ts +12 -2
  227. package/src/services/accounts/email-write.ts +23 -0
  228. package/src/services/accounts/groups.ts +15 -4
  229. package/src/services/accounts/identities.ts +249 -0
  230. package/src/services/accounts/identity-reconciliation.ts +87 -0
  231. package/src/services/accounts/ipa-data.ts +2 -2
  232. package/src/services/accounts/local-groups.ts +2 -2
  233. package/src/services/accounts/notification-sender.ts +12 -2
  234. package/src/services/accounts/posix.ts +45 -29
  235. package/src/services/accounts/users.ts +2 -2
  236. package/src/services/announcements/index.ts +69 -8
  237. package/src/services/app-approval.ts +11 -10
  238. package/src/services/audit/index.ts +1 -1
  239. package/src/services/cache-fill.ts +35 -0
  240. package/src/services/help/index.ts +109 -0
  241. package/src/services/help/maintenance.ts +25 -0
  242. package/src/services/help/store.ts +180 -0
  243. package/src/services/help/types.ts +46 -0
  244. package/src/services/identity/invocation-actor.ts +2 -2
  245. package/src/services/identity/key-config.ts +8 -5
  246. package/src/services/identity/key-ring.ts +1 -1
  247. package/src/services/identity/runtime-config.ts +6 -5
  248. package/src/services/index.ts +15 -0
  249. package/src/services/ipa/posix.ts +1 -1
  250. package/src/services/ipa/sync.ts +1 -1
  251. package/src/services/ipa/users.ts +268 -221
  252. package/src/services/legal-consent.ts +1 -1
  253. package/src/services/logging/index.ts +15 -13
  254. package/src/services/logging/trace.ts +2 -1
  255. package/src/services/logging/web-vitals.ts +81 -0
  256. package/src/services/mandates/index.ts +21 -4
  257. package/src/services/mandates/policy.ts +165 -2
  258. package/src/services/notifications/batches.ts +125 -57
  259. package/src/services/notifications/browser.ts +12 -0
  260. package/src/services/notifications/index.ts +0 -2
  261. package/src/services/notifications/platform.ts +0 -4
  262. package/src/services/oauth-tokens.ts +3 -3
  263. package/src/services/pdf/gotenberg.ts +131 -72
  264. package/src/services/pdf/index.ts +11 -8
  265. package/src/services/providers/local/users.ts +25 -16
  266. package/src/services/public-http.ts +101 -0
  267. package/src/services/rail-shortcuts.ts +95 -0
  268. package/src/services/rail-snapshot.ts +53 -0
  269. package/src/services/request-cache-redis.ts +29 -0
  270. package/src/services/session/index.ts +15 -2
  271. package/src/services/session/user.ts +62 -37
  272. package/src/services/settings/app.ts +5 -1
  273. package/src/services/settings/core-settings.ts +26 -35
  274. package/src/services/settings/defaults.ts +5 -0
  275. package/src/services/settings/index.ts +3 -3
  276. package/src/services/settings/store.ts +58 -84
  277. package/src/services/webauthn.ts +1 -1
  278. package/src/shared/ai-costs.ts +42 -0
  279. package/src/shared/ai-platform-prompt.ts +1 -1
  280. package/src/shared/ai-quotas.ts +136 -0
  281. package/src/shared/ai-usage.ts +6 -16
  282. package/src/shared/app-presentation.ts +21 -15
  283. package/src/shared/capability-messages.ts +14 -0
  284. package/src/shared/help.ts +6 -42
  285. package/src/shared/index.ts +3 -0
  286. package/src/shared/markdown/extensions/info-blocks.ts +1 -1
  287. package/src/ssr/AdminLayout.tsx +7 -5
  288. package/src/ssr/AdminSidebar.tsx +30 -16
  289. package/src/ssr/AppLaunchpad.island.tsx +33 -136
  290. package/src/ssr/AppLaunchpadPanel.tsx +181 -0
  291. package/src/ssr/BrowserPushRegistration.island.tsx +12 -0
  292. package/src/ssr/GlobalSearchDialog.tsx +167 -20
  293. package/src/ssr/GlobalSearchTrigger.island.tsx +33 -22
  294. package/src/ssr/Layout.tsx +17 -6
  295. package/src/ssr/LayoutBreadcrumbs.island.tsx +3 -5
  296. package/src/ssr/LayoutFooter.tsx +1 -1
  297. package/src/ssr/LayoutHeader.tsx +46 -14
  298. package/src/ssr/LayoutHelp.tsx +22 -23
  299. package/src/ssr/{HotkeysHelpRail.island.tsx → LayoutHelpTrigger.island.tsx} +18 -12
  300. package/src/ssr/LayoutRail.tsx +17 -9
  301. package/src/ssr/MinimalLayout.tsx +1 -1
  302. package/src/ssr/MobileNavigation.tsx +71 -0
  303. package/src/ssr/MobileProfileActions.tsx +49 -0
  304. package/src/ssr/ProfilePreferences.island.tsx +3 -36
  305. package/src/ssr/RailApps.island.tsx +2 -2
  306. package/src/ssr/RailEditor.tsx +17 -2
  307. package/src/ssr/WorkspaceNavigation.island.tsx +7 -0
  308. package/src/ssr/WorkspaceNavigationProvider.tsx +16 -0
  309. package/src/ssr/admin-navigation.ts +11 -1
  310. package/src/ssr/help.ts +29 -0
  311. package/src/ssr/islands/SearchBar.tsx +67 -0
  312. package/src/ssr/islands/index.ts +3 -1
  313. package/src/ssr/layout-context.ts +2 -2
  314. package/src/ssr/layout-help-search.ts +15 -0
  315. package/src/ssr/mobile-menu-history.ts +70 -0
  316. package/src/ssr/platform-messages.ts +16 -8
  317. package/src/ssr/profile-actions.ts +42 -0
  318. package/src/ssr/rail-context.ts +8 -4
  319. package/src/ssr/rail-messages.ts +2 -0
  320. package/src/ssr/rail-navigation.ts +3 -3
  321. package/src/ssr/workspace-navigation.ts +75 -0
  322. package/src/styles/effects.css +6 -6
  323. package/src/styles/global.css +54 -0
  324. package/src/styles/input.css +43 -41
  325. package/src/styles/resource-search.css +505 -0
  326. package/src/styles/utilities-feedback.css +13 -0
  327. package/src/styles/utilities-layout.css +1 -5
  328. package/src/styles/utilities-navigation.css +3 -39
  329. package/src/types/ambient.d.ts +5 -0
  330. package/src/workflows/ai/index.ts +2 -2
  331. package/src/workflows/ai/runtime.ts +19 -90
  332. package/src/workflows/ai/store.ts +2 -0
  333. package/src/workflows/ai/types.ts +6 -82
  334. package/src/workflows/ai-actions.ts +16 -13
  335. package/src/workflows/definition.ts +10 -5
  336. package/src/workflows/store/actions.ts +44 -20
  337. package/src/workflows/store/index.ts +4 -2
  338. package/src/workflows/store/runs.ts +36 -0
  339. package/src/workflows/store/worker-pool.ts +55 -0
  340. package/src/workflows/store/worker-runtime.ts +90 -0
  341. package/src/ai/kit-skill.ts +0 -22
  342. package/src/ai/usage-migrate.ts +0 -81
  343. package/src/browser/notification-target.ts +0 -27
  344. package/src/contracts/notification-live.ts +0 -66
  345. package/src/server/services/freeipa/test-certificates.ts +0 -44
  346. package/src/services/notifications/live.ts +0 -50
  347. package/src/services/session/test-fixture.ts +0 -18
  348. package/src/ssr/BrowserNotifications.island.tsx +0 -135
  349. package/src/ssr/islands/SearchBar.island.tsx +0 -80
@@ -1,5 +1,6 @@
1
1
  import { z } from "zod";
2
2
  import { CloudResourceRefSchema } from "../../contracts";
3
+ import { CapabilitySearchTagManifestSchema } from "../../contracts/capabilities";
3
4
 
4
5
  const TAG_PATTERN = /^[^\s#]+$/;
5
6
  const SEARCH_BASE_URL = "https://cloud.invalid";
@@ -29,26 +30,35 @@ const TagArraySchema = z.preprocess(
29
30
  .transform((tags) => [...new Set(tags.map((tag) => tag.toLowerCase()))]),
30
31
  );
31
32
 
32
- export const SearchQuerySchema = z.object({
33
- q: z
34
- .string()
35
- .max(500)
36
- .optional()
37
- .default("")
38
- .transform((query) => query.trim()),
39
- tag: TagArraySchema.optional().default([]),
40
- app: z.string().trim().min(1).max(120).optional(),
41
- require_reader: z
42
- .enum(["true"])
43
- .optional()
44
- .transform((value) => value === "true"),
45
- provider_limit: z.coerce.number().int().min(1).max(30).optional().default(10),
46
- });
33
+ export const SearchQuerySchema = z
34
+ .object({
35
+ q: z
36
+ .string()
37
+ .max(500)
38
+ .optional()
39
+ .default("")
40
+ .transform((query) => query.trim()),
41
+ tag: TagArraySchema.optional().default([]),
42
+ app: z.string().trim().min(1).max(120).optional(),
43
+ scope_tag: z.string().trim().min(1).max(64).regex(TAG_PATTERN).toLowerCase().optional(),
44
+ scope_type: CloudResourceRefSchema.shape.type.optional(),
45
+ scope_id: CloudResourceRefSchema.shape.id.optional(),
46
+ require_reader: z
47
+ .enum(["true"])
48
+ .optional()
49
+ .transform((value) => value === "true"),
50
+ provider_limit: z.coerce.number().int().min(1).max(30).optional().default(10),
51
+ })
52
+ .refine((value) => Boolean(value.scope_type) === Boolean(value.scope_id), { message: "Both scope_type and scope_id are required" })
53
+ .refine((value) => !value.scope_tag || (Boolean(value.app) && !value.scope_type), {
54
+ message: "A tag context requires an app and cannot include a resource scope",
55
+ });
47
56
 
48
57
  export const SearchAppSchema = z.object({
49
58
  id: z.string(),
50
59
  name: z.string(),
51
60
  icon: z.string(),
61
+ tags: z.array(CapabilitySearchTagManifestSchema).optional(),
52
62
  });
53
63
 
54
64
  export const SearchItemSchema = z.object({
@@ -72,6 +82,7 @@ export const SearchResponseSchema = z.object({
72
82
  items: z.array(SearchItemSchema),
73
83
  apps: z.array(SearchAppSchema),
74
84
  unsupportedTags: z.array(z.string()).optional(),
85
+ failedApps: z.array(z.string()).optional(),
75
86
  });
76
87
 
77
88
  export type SearchApp = z.infer<typeof SearchAppSchema>;
@@ -16,7 +16,7 @@ const PROVIDER_LATENCY_MS = 5;
16
16
  const ISSUER = "https://cloud.test";
17
17
 
18
18
  const capabilities = defineCapabilities({
19
- protocolVersion: 1,
19
+ protocolVersion: 2,
20
20
  types: { item: { title: "Item", description: "A benchmark item." } },
21
21
  queries: {
22
22
  search: {
package/src/api/search.ts CHANGED
@@ -1,6 +1,7 @@
1
1
  import { Hono, type MiddlewareHandler } from "hono";
2
2
  import { describeRoute } from "hono-openapi";
3
3
  import { readBoundedJson } from "../_internal/bounded-json";
4
+ import { resolveCapabilityManifestPresentation } from "../_internal/capabilities";
4
5
  import { listCapabilities } from "../_internal/registry";
5
6
  import type { CapabilityRegistryEntry } from "../contracts";
6
7
  import {
@@ -20,6 +21,7 @@ import { LOCALE_HEADER } from "../shared/locale";
20
21
  import { type SearchItem, SearchItemSchema, SearchQuerySchema, SearchResponseSchema } from "./search/schemas";
21
22
 
22
23
  const log = logger("search");
24
+ const SearchProviderResultSchema = capabilityResultSchema(UniversalSearchDataSchema);
23
25
 
24
26
  /**
25
27
  * Maximum items returned to the client after merging across providers.
@@ -28,7 +30,6 @@ const log = logger("search");
28
30
  const GLOBAL_RESULT_LIMIT = 30;
29
31
  const PROVIDER_CONCURRENCY = 8;
30
32
  const PROVIDER_TIMEOUT_MS = 8_000;
31
- const SIGNING_TIMEOUT_MS = 500;
32
33
 
33
34
  type HttpSearchProvider = {
34
35
  appId: string;
@@ -36,6 +37,7 @@ type HttpSearchProvider = {
36
37
  appIcon: string;
37
38
  endpoint: string;
38
39
  tags: string[];
40
+ scopeTypes: string[];
39
41
  typeIds: Set<string>;
40
42
  schemaHash: string;
41
43
  };
@@ -55,6 +57,7 @@ const getSearchProviders = (entries: CapabilityRegistryEntry[]): HttpSearchProvi
55
57
  appIcon: entry.appIcon,
56
58
  endpoint: `${entry.endpoint}/queries/${encodeURIComponent(query.localId)}`,
57
59
  tags: query.universalSearch.tags.flatMap((tag) => [tag.tag, ...(tag.aliases ?? [])]),
60
+ scopeTypes: (query.universalSearch.scopeTypes ?? []).map((type) => `${entry.appId}.${type}`),
58
61
  typeIds: new Set(entry.manifest.types.map((type) => `${entry.appId}.${type.localId}`)),
59
62
  schemaHash: query.schemaHash,
60
63
  },
@@ -125,8 +128,8 @@ const settleBounded = async <T, R>(items: readonly T[], concurrency: number, run
125
128
  const waitWithin = <T>(value: Promise<T>, signal: AbortSignal): Promise<T> =>
126
129
  new Promise<T>((resolve, reject) => {
127
130
  const aborted = () => reject(signal.reason);
128
- if (signal.aborted) return aborted();
129
- signal.addEventListener("abort", aborted, { once: true });
131
+ // The work has already started; observe its rejection even if the client
132
+ // disconnected before we began waiting.
130
133
  value.then(
131
134
  (result) => {
132
135
  signal.removeEventListener("abort", aborted);
@@ -137,6 +140,8 @@ const waitWithin = <T>(value: Promise<T>, signal: AbortSignal): Promise<T> =>
137
140
  reject(error);
138
141
  },
139
142
  );
143
+ if (signal.aborted) return aborted();
144
+ signal.addEventListener("abort", aborted, { once: true });
140
145
  });
141
146
 
142
147
  /**
@@ -183,11 +188,26 @@ export const createSearchRoutes = (dependencies: SearchRouteDependencies = {}) =
183
188
  );
184
189
  }
185
190
  const providers = getSearchProviders(entries);
186
- const apps = [
187
- ...new Map(
188
- providers.map((provider) => [provider.appId, { id: provider.appId, name: provider.appName, icon: provider.appIcon }]),
189
- ).values(),
190
- ].sort((a, b) => a.name.localeCompare(b.name));
191
+ const requestLocale = preferredLocale(c.req.raw.headers) ?? "en";
192
+ const apps = entries
193
+ .flatMap((entry) => {
194
+ const manifest = resolveCapabilityManifestPresentation(entry.manifest, entry.presentation, requestLocale);
195
+ const queries = manifest.queries.filter((operation) => operation.universalSearch);
196
+ if (!queries.length) return [];
197
+ return [
198
+ {
199
+ id: entry.appId,
200
+ name: entry.appName,
201
+ icon: entry.appIcon,
202
+ tags: [
203
+ ...new Map(
204
+ queries.flatMap((operation) => (operation.universalSearch?.tags ?? []).map((tag) => [tag.tag, tag] as const)),
205
+ ).values(),
206
+ ],
207
+ },
208
+ ];
209
+ })
210
+ .sort((a, b) => a.name.localeCompare(b.name));
191
211
  const readableTypes = new Set(
192
212
  entries.flatMap((entry) => entry.manifest.types.filter((type) => type.reader).map((type) => `${entry.appId}.${type.localId}`)),
193
213
  );
@@ -199,11 +219,19 @@ export const createSearchRoutes = (dependencies: SearchRouteDependencies = {}) =
199
219
  // returned to the client so it can render a helpful empty state.
200
220
  const knownTags = new Set(providers.flatMap((p) => p.tags));
201
221
  const unsupportedTags = query.tag.filter((t) => !knownTags.has(t));
202
- const appProviders = query.app ? providers.filter((provider) => provider.appId === query.app) : providers;
222
+ const scope = query.scope_type && query.scope_id ? { type: query.scope_type, id: query.scope_id } : undefined;
223
+ const appProviders = providers.filter(
224
+ (provider) =>
225
+ (!query.app || provider.appId === query.app) &&
226
+ (!scope || provider.scopeTypes.includes(scope.type)) &&
227
+ (!query.scope_tag || provider.tags.includes(query.scope_tag)),
228
+ );
229
+ if ((scope || query.scope_tag) && !appProviders.length)
230
+ return c.json({ code: "INVALID_SEARCH_SCOPE", message: "Search scope is unavailable" }, 400);
203
231
  const active =
204
232
  query.tag.length === 0 ? appProviders : appProviders.filter((provider) => provider.tags.some((tag) => query.tag.includes(tag)));
205
233
 
206
- if (query.q.length === 0 && query.tag.length === 0 && !query.app) {
234
+ if (query.q.length === 0 && query.tag.length === 0 && !query.app && !scope) {
207
235
  return c.json({ query: "", count: 0, items: [], apps });
208
236
  }
209
237
 
@@ -225,7 +253,6 @@ export const createSearchRoutes = (dependencies: SearchRouteDependencies = {}) =
225
253
  // The same request-wide budget covers signing and provider I/O.
226
254
  const providerDeadline = AbortSignal.timeout(PROVIDER_TIMEOUT_MS);
227
255
  const fanoutSignal = AbortSignal.any([c.req.raw.signal, providerDeadline]);
228
- const signingSignal = AbortSignal.any([fanoutSignal, AbortSignal.timeout(SIGNING_TIMEOUT_MS)]);
229
256
  // Guard the whole issuance batch once, then reuse the prepared signer for
230
257
  // every target. This is one Postgres check per outer search request, not
231
258
  // one check per provider.
@@ -251,17 +278,17 @@ export const createSearchRoutes = (dependencies: SearchRouteDependencies = {}) =
251
278
  signer,
252
279
  issuer: signer.issuer,
253
280
  }),
254
- signingSignal,
281
+ fanoutSignal,
255
282
  ),
256
283
  ),
257
- { signal: signingSignal, timeoutMs: SIGNING_TIMEOUT_MS },
284
+ { signal: fanoutSignal, timeoutMs: PROVIDER_TIMEOUT_MS },
258
285
  ),
259
- signingSignal,
286
+ fanoutSignal,
260
287
  );
261
288
  }
262
289
  } catch {
263
290
  log.warn("Search invocation authority unavailable", {
264
- reason: signingSignal.aborted ? "signing_deadline" : "signing_guard_failed",
291
+ reason: fanoutSignal.aborted ? "signing_deadline" : "signing_guard_failed",
265
292
  });
266
293
  return c.json(
267
294
  { code: CAPABILITY_FRAMEWORK_ERROR_CODES.appUnavailable, message: "Search invocation authority is currently unavailable" },
@@ -275,7 +302,9 @@ export const createSearchRoutes = (dependencies: SearchRouteDependencies = {}) =
275
302
  // Scope tags to those this provider declared. Apps no longer need
276
303
  // their own gate — the framework guarantees they only see tags
277
304
  // they understand.
278
- const scopedTags = query.tag.filter((t) => provider.tags.includes(t));
305
+ const scopedTags = [...new Set([...(query.scope_tag ? [query.scope_tag] : []), ...query.tag])].filter((tag) =>
306
+ provider.tags.includes(tag),
307
+ );
279
308
 
280
309
  const headers = await (async () => {
281
310
  const signed = await signedInvocations?.[index];
@@ -305,6 +334,7 @@ export const createSearchRoutes = (dependencies: SearchRouteDependencies = {}) =
305
334
  input: {
306
335
  query: query.q,
307
336
  tags: scopedTags,
337
+ ...(scope ? { scope } : {}),
308
338
  limit: effectiveProviderLimit,
309
339
  },
310
340
  }),
@@ -317,7 +347,7 @@ export const createSearchRoutes = (dependencies: SearchRouteDependencies = {}) =
317
347
 
318
348
  const parsedBody = await readBoundedJson(res, CAPABILITY_MAX_RESULT_BYTES);
319
349
  if (!parsedBody.ok) throw new Error(`Search provider ${provider.appId} returned invalid or oversized JSON`);
320
- const envelope = capabilityResultSchema(UniversalSearchDataSchema).safeParse(parsedBody.data);
350
+ const envelope = SearchProviderResultSchema.safeParse(parsedBody.data);
321
351
  if (!envelope.success) throw new Error(`Search provider ${provider.appId} returned an invalid capability result`);
322
352
  const results = envelope.data.data;
323
353
  const validItems: SearchItem[] = [];
@@ -399,6 +429,13 @@ export const createSearchRoutes = (dependencies: SearchRouteDependencies = {}) =
399
429
  items: sliced,
400
430
  apps,
401
431
  ...(unsupportedTags.length > 0 ? { unsupportedTags } : {}),
432
+ ...(settled.some((result) => result.status === "rejected")
433
+ ? {
434
+ failedApps: [
435
+ ...new Set(active.filter((_, index) => settled[index]?.status === "rejected").map((provider) => provider.appId)),
436
+ ],
437
+ }
438
+ : {}),
402
439
  });
403
440
  },
404
441
  );