@k2b/cloud 0.39.0 → 0.41.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 (237) hide show
  1. package/README.md +8 -0
  2. package/package.json +11 -4
  3. package/src/_internal/app-favicon.ts +4 -2
  4. package/src/_internal/capabilities.ts +72 -48
  5. package/src/_internal/capability-reviews.ts +81 -0
  6. package/src/_internal/capability-schemas.ts +29 -0
  7. package/src/_internal/capability-sentences.ts +217 -1
  8. package/src/_internal/capability-streams.ts +7 -0
  9. package/src/_internal/define-app.ts +49 -18
  10. package/src/_internal/font-preloads.ts +2 -1
  11. package/src/_internal/heartbeat.ts +203 -14
  12. package/src/_internal/registry.ts +28 -1
  13. package/src/_internal/runtime-watcher.ts +6 -0
  14. package/src/_internal/setup-lock.ts +103 -0
  15. package/src/_internal/static-assets.ts +52 -3
  16. package/src/_internal/status-preserving-ssr.ts +2 -0
  17. package/src/access/PermissionEditor.tsx +2 -2
  18. package/src/account/EntitySearch.tsx +1 -1
  19. package/src/ai/action-review.ts +101 -0
  20. package/src/ai/background-review.ts +72 -0
  21. package/src/ai/capabilities.ts +29 -11
  22. package/src/ai/capability-execution.ts +76 -43
  23. package/src/ai/capability-result.ts +95 -0
  24. package/src/ai/chat/action-card.ts +464 -0
  25. package/src/ai/chat/action-messages.ts +129 -0
  26. package/src/ai/chat/action-question.browser-harness.tsx +245 -0
  27. package/src/ai/chat/action-question.fixtures.ts +733 -0
  28. package/src/ai/chat/action-question.tsx +680 -0
  29. package/src/ai/chat/action-text.ts +288 -0
  30. package/src/ai/chat/blocks.tsx +19 -25
  31. package/src/ai/chat/message-actions.tsx +6 -0
  32. package/src/ai/chat/tool-action-words.ts +31 -0
  33. package/src/ai/chat/tool-action.tsx +456 -0
  34. package/src/ai/chat/turn-view.tsx +10 -160
  35. package/src/ai/chat/view-block.tsx +15 -5
  36. package/src/ai/chat-task-contracts.ts +4 -1
  37. package/src/ai/chat-task-reasons-migrate.ts +7 -0
  38. package/src/ai/chat-task-reasons.ts +1 -1
  39. package/src/ai/chat-tasks.ts +55 -25
  40. package/src/ai/client/controller.ts +59 -0
  41. package/src/ai/default-tools.ts +9 -1
  42. package/src/ai/executor.ts +198 -39
  43. package/src/ai/firecrawl-tools.ts +15 -2
  44. package/src/ai/index.ts +3 -0
  45. package/src/ai/migrate.ts +26 -2
  46. package/src/ai/open-questions.ts +172 -0
  47. package/src/ai/projects-routes.ts +3 -0
  48. package/src/ai/protocol.ts +14 -2
  49. package/src/ai/question-text.ts +8 -0
  50. package/src/ai/recent-resources.ts +240 -0
  51. package/src/ai/recent-sent-refs.ts +38 -0
  52. package/src/ai/routes.ts +195 -5
  53. package/src/ai/runtime.ts +1 -0
  54. package/src/ai/skills-routes.ts +3 -0
  55. package/src/ai/skills.ts +10 -1
  56. package/src/ai/store.ts +245 -24
  57. package/src/ai/testing.ts +36 -0
  58. package/src/ai/tool-audit.ts +8 -0
  59. package/src/ai/tool-sentences.ts +152 -34
  60. package/src/ai/tool-wording.ts +36 -8
  61. package/src/ai/tools.ts +8 -3
  62. package/src/ai/types.ts +43 -4
  63. package/src/api/accounts-entities.ts +2 -1
  64. package/src/api/admin-ai-projects.ts +2 -0
  65. package/src/api/admin-ai-skills.ts +2 -0
  66. package/src/api/admin-ai-usage.ts +2 -0
  67. package/src/api/admin-geoip.ts +80 -0
  68. package/src/api/admin-identity.ts +3 -0
  69. package/src/api/admin-lifecycle.ts +3 -0
  70. package/src/api/admin-outgoing-mail.ts +2 -0
  71. package/src/api/app-approval-actions.ts +31 -7
  72. package/src/api/app-approval.ts +46 -5
  73. package/src/api/approvals-loader.ts +23 -1
  74. package/src/api/approvals.ts +2 -0
  75. package/src/api/auth/schemas.ts +3 -0
  76. package/src/api/auth.ts +67 -12
  77. package/src/api/capabilities.ts +44 -11
  78. package/src/api/capability-streams.ts +12 -0
  79. package/src/api/help.ts +4 -28
  80. package/src/api/index.ts +6 -1
  81. package/src/api/lasting-access-messages.ts +17 -0
  82. package/src/api/mcp.ts +30 -3
  83. package/src/api/me-panel-consent.ts +71 -0
  84. package/src/api/me.ts +159 -36
  85. package/src/api/search.ts +3 -1
  86. package/src/api/sync-ops.ts +2 -0
  87. package/src/api/widgets.ts +3 -0
  88. package/src/approvals/Approvals.tsx +166 -262
  89. package/src/approvals/approval-card.ts +68 -0
  90. package/src/approvals/messages.ts +12 -24
  91. package/src/browser/CloudResourceSearch.browser-harness.tsx +10 -2
  92. package/src/browser/app-approval.ts +54 -5
  93. package/src/browser/commands.ts +22 -6
  94. package/src/browser/composer-catalog.ts +45 -3
  95. package/src/browser/composer-insert.ts +10 -0
  96. package/src/browser/composer-open-messages.ts +17 -0
  97. package/src/browser/composer-open.ts +110 -0
  98. package/src/browser/discoverable-command.ts +6 -0
  99. package/src/browser/hints.ts +6 -0
  100. package/src/browser/live-connection.ts +26 -2
  101. package/src/browser/navigation-apps.ts +7 -0
  102. package/src/browser/panel-context-bridge.ts +30 -0
  103. package/src/browser/panel-context-receiver.ts +53 -0
  104. package/src/browser/panel-menu.tsx +56 -0
  105. package/src/browser/panel-messages.ts +11 -0
  106. package/src/browser/panel-shortcuts.ts +31 -0
  107. package/src/browser/panel-slot.ts +412 -10
  108. package/src/browser/panel.ts +37 -0
  109. package/src/browser/search-commands.ts +6 -3
  110. package/src/capabilities/client.ts +8 -2
  111. package/src/capabilities/reviews.ts +205 -0
  112. package/src/capabilities/server.ts +10 -3
  113. package/src/capabilities/testing.ts +47 -0
  114. package/src/capabilities/types.ts +2 -2
  115. package/src/cli/account.ts +13 -3
  116. package/src/cli/admin/geoip.ts +115 -0
  117. package/src/cli/admin/index.ts +3 -0
  118. package/src/contracts/app-approval.ts +123 -16
  119. package/src/contracts/approvals.ts +2 -2
  120. package/src/contracts/capabilities.ts +410 -18
  121. package/src/contracts/geoip.ts +42 -0
  122. package/src/contracts/index.ts +3 -0
  123. package/src/contracts/panel-context-plain.ts +99 -0
  124. package/src/contracts/panel-context.ts +93 -0
  125. package/src/contracts/seen-hints.ts +35 -0
  126. package/src/contracts/shared.ts +3 -0
  127. package/src/server/client-ip.ts +5 -0
  128. package/src/server/help-payload.ts +38 -0
  129. package/src/server/middleware/auth.ts +13 -1
  130. package/src/server/middleware/middleware.ts +9 -0
  131. package/src/server/middleware/no-transform.ts +19 -0
  132. package/src/server/middleware/rate-limit.ts +4 -6
  133. package/src/server/request-context/address.ts +45 -0
  134. package/src/server/request-context/geoip.ts +380 -0
  135. package/src/server/request-context/index.ts +33 -0
  136. package/src/server/request-context/user-agent.ts +95 -0
  137. package/src/services/accounts/app.ts +23 -3
  138. package/src/services/accounts/users.ts +12 -1
  139. package/src/services/app-approval.ts +382 -59
  140. package/src/services/approvals/device.ts +49 -31
  141. package/src/services/approvals/index.ts +7 -5
  142. package/src/services/approvals/list.ts +27 -6
  143. package/src/services/approvals/question.ts +68 -0
  144. package/src/services/audit/index.ts +1 -0
  145. package/src/services/auth-flows/ipa.ts +39 -2
  146. package/src/services/auth-flows/notification-sender.ts +3 -0
  147. package/src/services/auth-flows/password-reset.ts +32 -6
  148. package/src/services/geoip/index.ts +164 -0
  149. package/src/services/geoip/store.ts +30 -0
  150. package/src/services/geoip/upload.ts +150 -0
  151. package/src/services/identity/routes.ts +5 -1
  152. package/src/services/index.ts +13 -0
  153. package/src/services/known-browsers.ts +312 -0
  154. package/src/services/login-address-store.ts +51 -0
  155. package/src/services/login-context.ts +118 -0
  156. package/src/services/mandates/index.ts +3 -0
  157. package/src/services/mandates/policy.ts +15 -3
  158. package/src/services/notifications/email-frame.ts +1 -1
  159. package/src/services/oauth-tokens.ts +16 -2
  160. package/src/services/panel-context-consent.ts +58 -0
  161. package/src/services/pdf/markdown.ts +160 -2
  162. package/src/services/service-account-credentials.ts +21 -0
  163. package/src/services/session/index.ts +296 -41
  164. package/src/services/session/recent.ts +13 -0
  165. package/src/services/webauthn.ts +12 -0
  166. package/src/shared/email-html.ts +2 -0
  167. package/src/shared/help.ts +3 -0
  168. package/src/shared-text/cursor.ts +22 -0
  169. package/src/shared-text/documents.ts +142 -0
  170. package/src/shared-text/events.ts +43 -0
  171. package/src/shared-text/index.ts +12 -0
  172. package/src/shared-text/log.ts +48 -0
  173. package/src/shared-text/text-diff.ts +23 -0
  174. package/src/shared-text/types.ts +41 -0
  175. package/src/ssr/AdminLayout.tsx +2 -0
  176. package/src/ssr/AppLaunchpadPanel.tsx +2 -2
  177. package/src/ssr/CloudShell.island.tsx +10 -2
  178. package/src/ssr/HelpPanelArticle.island.tsx +308 -0
  179. package/src/ssr/HelpPanelBack.island.tsx +32 -0
  180. package/src/ssr/HelpPanelMenu.island.tsx +133 -0
  181. package/src/ssr/HelpPanelSearch.island.tsx +123 -0
  182. package/src/ssr/HelpPanelShortcuts.island.tsx +22 -0
  183. package/src/ssr/HelpPanelView.island.tsx +27 -0
  184. package/src/ssr/Layout.tsx +25 -2
  185. package/src/ssr/LayoutHelp.browser-harness.tsx +2 -0
  186. package/src/ssr/LayoutHelp.tsx +222 -419
  187. package/src/ssr/LayoutHelpPanel.browser-harness.tsx +135 -0
  188. package/src/ssr/LayoutHelpPanel.server.browser-harness.tsx +131 -0
  189. package/src/ssr/LayoutHelpPanel.server.tsx +115 -0
  190. package/src/ssr/LayoutHelpTrigger.tsx +27 -2
  191. package/src/ssr/MobileNavigation.tsx +2 -2
  192. package/src/ssr/PanelLayout.tsx +9 -1
  193. package/src/ssr/PanelRuntime.island.tsx +7 -0
  194. package/src/ssr/PanelSlot.tsx +53 -3
  195. package/src/ssr/ShellRuntime.island.tsx +11 -2
  196. package/src/ssr/__snapshots__/LayoutHelp.ssr.render.test.tsx.snap +29 -0
  197. package/src/ssr/app-color.ts +14 -9
  198. package/src/ssr/app-navigation.ts +16 -3
  199. package/src/ssr/compact-shell.browser-harness.ts +30 -22
  200. package/src/ssr/dialog-app-colors.browser-harness.tsx +43 -0
  201. package/src/ssr/help-messages.ts +4 -0
  202. package/src/ssr/help.ts +21 -0
  203. package/src/ssr/hints.ts +25 -0
  204. package/src/ssr/index.ts +3 -0
  205. package/src/ssr/layout-context.ts +2 -0
  206. package/src/ssr/layout-help-article.server.ts +50 -0
  207. package/src/ssr/layout-help-panel-controls.tsx +43 -0
  208. package/src/ssr/layout-help-panel-document.ts +67 -0
  209. package/src/ssr/layout-help-panel-lifecycle.ts +32 -0
  210. package/src/ssr/layout-help-panel-menu.tsx +113 -0
  211. package/src/ssr/layout-help-panel-modules.ts +7 -0
  212. package/src/ssr/layout-help-panel-reader.tsx +159 -0
  213. package/src/ssr/layout-help-panel-search.tsx +69 -0
  214. package/src/ssr/layout-help-panel-shortcuts.ts +7 -0
  215. package/src/ssr/layout-help-panel-state.ts +141 -0
  216. package/src/ssr/layout-help-panel-types.ts +19 -0
  217. package/src/ssr/layout-help-start.ts +33 -7
  218. package/src/ssr/layout-help-views.tsx +484 -0
  219. package/src/ssr/panel-consent.ts +29 -0
  220. package/src/ssr/panel-context.ts +10 -0
  221. package/src/ssr/panel-idle.ts +9 -0
  222. package/src/ssr/panel-slot.browser-harness.tsx +53 -4
  223. package/src/ssr/platform-messages.ts +18 -0
  224. package/src/ssr/shell-data.tsx +2 -1
  225. package/src/styles/approvals.css +6 -0
  226. package/src/styles/effects.css +213 -9
  227. package/src/styles/file-chooser.css +2 -1
  228. package/src/styles/global.css +49 -12
  229. package/src/styles/panel.css +47 -4
  230. package/src/styles/resource-search.css +6 -2
  231. package/src/styles/tokens.css +17 -11
  232. package/src/styles/utilities-buttons.css +3 -2
  233. package/src/styles/utilities-data.css +2 -1
  234. package/src/styles/utilities-feedback.css +2 -2
  235. package/src/styles/utilities-layout.css +3 -13
  236. package/src/styles/utilities-markdown-editor.css +6 -6
  237. package/src/styles/utilities-navigation.css +17 -22
@@ -30,10 +30,23 @@ export const APP_ACTION_LIMITS = {
30
30
  valueChars: 300,
31
31
  } as const;
32
32
  /** Commands a Cloud serves beyond the first version, announced in `/info` as `features`. */
33
- export const APP_APPROVAL_FEATURES = ["actions", "terminal-logins"] as const;
34
-
35
- export const APP_APPROVAL_ACCEPT = ["terminal-logins"] as const;
33
+ export const APP_APPROVAL_FEATURES = ["actions", "terminal-logins", "login-context", "device-changes"] as const;
34
+ /**
35
+ * Optional inbox parts a device asks for in its signed `inbox` command. Without `accept`, the command, its
36
+ * signed message and the inbox answer keep their first form byte for byte.
37
+ */
38
+ export const APP_APPROVAL_ACCEPT = ["terminal-logins", "login-context", "device-changes"] as const;
36
39
  export type AppApprovalAccept = (typeof APP_APPROVAL_ACCEPT)[number];
40
+ export const APP_APPROVAL_PENDING_ACCEPT = ["login-context"] as const;
41
+ export type AppApprovalPendingAccept = (typeof APP_APPROVAL_PENDING_ACCEPT)[number];
42
+
43
+ const acceptList = <T extends readonly [string, ...string[]]>(values: T) =>
44
+ z
45
+ .array(z.enum(values))
46
+ .min(1)
47
+ .max(8)
48
+ .refine((entries) => new Set(entries).size === entries.length)
49
+ .optional();
37
50
 
38
51
  const Id = z.string().uuid();
39
52
  const Secret = z.string().regex(/^[A-Za-z0-9_-]{42}[AEIMQUYcgkosw048]$/);
@@ -70,10 +83,14 @@ export const AppLoginStartSchema = z
70
83
  export const AppLoginReferenceSchema = z.object({ requestId: Id, browserSecret: Secret }).strict();
71
84
  export const AppDeviceMutationSchema = z.discriminatedUnion("operation", [
72
85
  z.object({ operation: z.literal("rename"), deviceId: Id, name: z.string().trim().min(1).max(80) }).strict(),
73
- z.object({ operation: z.literal("revoke"), deviceId: Id }).strict(),
86
+ /**
87
+ * Asks the account's paired devices to approve removing this device. `endSessions` (default `true`) also ends
88
+ * the sign-ins this device approved, except the requesting session.
89
+ */
90
+ z.object({ operation: z.literal("revoke"), deviceId: Id, endSessions: z.boolean().optional() }).strict(),
74
91
  ]);
75
92
  export const AppDeviceCommandSchema = z.discriminatedUnion("operation", [
76
- z.object({ operation: z.literal("pending") }).strict(),
93
+ z.object({ operation: z.literal("pending"), accept: acceptList(APP_APPROVAL_PENDING_ACCEPT) }).strict(),
77
94
  z.object({ operation: z.literal("revoke") }).strict(),
78
95
  /** Reads the signing device's own account and device record, e.g. to show which account it signs in. */
79
96
  z.object({ operation: z.literal("account") }).strict(),
@@ -83,18 +100,25 @@ export const AppDeviceCommandSchema = z.discriminatedUnion("operation", [
83
100
  z
84
101
  .object({
85
102
  operation: z.literal("inbox"),
86
- accept: z
87
- .array(z.enum(APP_APPROVAL_ACCEPT))
88
- .min(1)
89
- .max(8)
90
- .refine((values) => new Set(values).size === values.length)
91
- .optional(),
103
+ /** Optional inbox parts this device shows; only send values the Cloud lists in `/info` `features`. */
104
+ accept: acceptList(APP_APPROVAL_ACCEPT),
92
105
  })
93
106
  .strict(),
94
107
  /** One open Action approval as the device shows it, with the digest the device recomputes before it decides. */
95
108
  z.object({ operation: z.literal("action"), actionId: Id }).strict(),
96
109
  /** Decides an Action approval. `digest` binds the decision to exactly the view the person saw. */
97
110
  z.object({ operation: z.literal("act"), actionId: Id, digest: Digest, decision: z.enum(["approve", "deny"]) }).strict(),
111
+ /** Decides a request to pair or remove a device of the signing device's account. Binds every fact the device showed. */
112
+ z
113
+ .object({
114
+ operation: z.literal("change"),
115
+ changeId: Id,
116
+ kind: z.enum(["pair", "revoke"]),
117
+ targetId: Id,
118
+ endSessions: z.boolean(),
119
+ decision: z.enum(["approve", "deny"]),
120
+ })
121
+ .strict(),
98
122
  z
99
123
  .object({
100
124
  operation: z.literal("decide"),
@@ -134,7 +158,8 @@ export const appDeviceProofMessage = (proof: AppDeviceProof): string => {
134
158
  c.operation,
135
159
  ...(c.operation === "decide" ? [c.requestId, c.challenge, c.comparison, c.decision] : []),
136
160
  ...(c.operation === "push" ? [c.token] : []),
137
- ...(c.operation === "inbox" && c.accept !== undefined ? [c.accept] : []),
161
+ ...((c.operation === "inbox" || c.operation === "pending") && c.accept !== undefined ? [c.accept] : []),
162
+ ...(c.operation === "change" ? [c.changeId, c.kind, c.targetId, c.endSessions, c.decision] : []),
138
163
  ...(c.operation === "action" ? [c.actionId] : []),
139
164
  ...(c.operation === "act" ? [c.actionId, c.digest, c.decision] : []),
140
165
  ]);
@@ -190,6 +215,44 @@ export const AppTerminalLoginSchema = z
190
215
  })
191
216
  .strict();
192
217
  export type AppTerminalLogin = z.infer<typeof AppTerminalLoginSchema>;
218
+ export const APP_LOGIN_CONTEXT_BROWSERS = ["firefox", "chrome", "safari", "edge", "samsung", "opera", "other"] as const;
219
+ export const APP_LOGIN_CONTEXT_OSES = ["windows", "macos", "mac-or-ipad", "ios", "android", "linux", "chromeos", "other"] as const;
220
+ const ContextName = PlainText(96)
221
+ .min(1)
222
+ .refine((value) => new TextEncoder().encode(value).length <= 96);
223
+ /** Core alone computes the level. The app displays it and never derives its own.
224
+ * Terminal logins never carry context. Opted-in browser logins carry it unless
225
+ * started before the upgrade or omitted to fit the response budget.
226
+ * History is null during introduction; sameNetwork is a positive, transient hint. */
227
+ export const AppLoginContextSchema = z
228
+ .object({
229
+ /** Computed by Core only; descriptive signals never change it. */
230
+ level: z.enum(["info", "hint", "warn"]),
231
+ browser: z.enum(APP_LOGIN_CONTEXT_BROWSERS),
232
+ os: z.enum(APP_LOGIN_CONTEXT_OSES),
233
+ country: z
234
+ .string()
235
+ .regex(/^[A-Z]{2}$/)
236
+ .nullable(),
237
+ city: ContextName.nullable(),
238
+ /** Operator name, never a VPN or data-center classification. */
239
+ network: ContextName.nullable(),
240
+ history: z
241
+ .object({
242
+ /** This browser's first_seen_at for the account, or null when unknown. */
243
+ knownSince: DateTime.nullable(),
244
+ /** A request country, geoip_since set, and no sign-in from it inside the 90-day window. */
245
+ newCountry: z.boolean(),
246
+ /** geoip_since only when less than 90 days ago; the app can say "since {date}". */
247
+ countriesSince: DateTime.nullable(),
248
+ })
249
+ .strict()
250
+ .nullable(),
251
+ sameNetwork: z.literal(true).optional(),
252
+ })
253
+ .strict();
254
+ export type AppLoginContext = z.infer<typeof AppLoginContextSchema>;
255
+
193
256
  export const AppPendingLoginSchema = z
194
257
  .object({
195
258
  requestId: Id,
@@ -198,6 +261,7 @@ export const AppPendingLoginSchema = z
198
261
  createdAt: DateTime,
199
262
  expiresAt: DateTime,
200
263
  terminal: AppTerminalLoginSchema.optional(),
264
+ context: AppLoginContextSchema.optional(),
201
265
  })
202
266
  .strict();
203
267
  export const AppPairingPayloadSchema = z
@@ -251,8 +315,29 @@ export const AppPairingClaimResultSchema = z.object({ deviceId: Id, comparison:
251
315
  export const AppPairingResultSchema = z
252
316
  .object({ state: z.enum(["pending", "claimed", "confirmed", "cancelled"]), deviceId: Id.nullable(), comparison: Comparison.nullable() })
253
317
  .strict();
318
+ /** Approval of a pairing by one of the account's paired devices, as the initiating browser follows it. */
319
+ export const AppDeviceChangeStatusSchema = z
320
+ .object({ state: z.enum(["pending", "approved", "denied", "cancelled", "expired"]), expiresAt: DateTime })
321
+ .strict();
322
+ export type AppDeviceChangeStatus = z.infer<typeof AppDeviceChangeStatusSchema>;
254
323
  /** Initiating Cloud session only; never returned to an unpaired device. */
255
- export const AppPairingInspectionSchema = AppPairingResultSchema.extend({ name: z.string().nullable(), userId: Id });
324
+ export const AppPairingInspectionSchema = AppPairingResultSchema.extend({
325
+ name: z.string().nullable(),
326
+ userId: Id,
327
+ /** Set once the code was confirmed while the account already had a paired device: one of them approves. */
328
+ approval: AppDeviceChangeStatusSchema.nullable(),
329
+ });
330
+ /** `approval` is set when one of the account's paired devices must approve before the device is paired. */
331
+ export const AppPairingConfirmResultSchema = z.object({ deviceId: Id, approval: AppDeviceChangeStatusSchema.nullable() }).strict();
332
+ export const AppDeviceChangeReferenceSchema = z.object({ changeId: Id }).strict();
333
+ /** A removal by its id, or this session's newest removal of a device, e.g. after the answer that named it was lost. */
334
+ export const AppDeviceChangeLookupSchema = z.union([AppDeviceChangeReferenceSchema, z.object({ deviceId: Id }).strict()]);
335
+ export const AppDeviceRemovalStatusSchema = AppDeviceChangeStatusSchema.extend({ changeId: Id, endSessions: z.boolean() }).strict();
336
+ /** A device that is already revoked answers `revoked`; an active one waits for approval on a paired device. */
337
+ export const AppDeviceRevokeResultSchema = z.union([
338
+ z.object({ state: z.literal("revoked") }).strict(),
339
+ z.object({ state: z.literal("pending"), changeId: Id, expiresAt: DateTime }).strict(),
340
+ ]);
256
341
  /** Answer to the `account` command: only the signing device's own account, never secrets or other users. */
257
342
  export const AppDeviceAccountSchema = z
258
343
  .object({
@@ -275,14 +360,14 @@ const actionFields = {
275
360
  createdAt: DateTime,
276
361
  expiresAt: DateTime,
277
362
  };
278
- /** One inbox row. `sentence` is the start of the app's sentence; read the Action for everything the person approves. */
363
+ /** One inbox row. `sentence` is the start of the plain question; read the Action for everything the person approves. */
279
364
  export const AppActionSummarySchema = z.object({ ...actionFields, sentence: PlainText(APP_ACTION_LIMITS.summaryChars).min(1) }).strict();
280
365
  export const AppActionViewSchema = z
281
366
  .object({
282
367
  ...actionFields,
283
- /** What the Action does, in the app's words and the person's language. */
368
+ /** The plain Assistant question, in the originating turn's language. */
284
369
  sentence: PlainText(APP_ACTION_LIMITS.sentenceChars).min(1),
285
- /** The app's review of this call, or `null`. */
370
+ /** The question's reason line, or `null` for older snapshots. */
286
371
  message: PlainText(APP_ACTION_LIMITS.messageChars).nullable(),
287
372
  details: z
288
373
  .array(
@@ -316,6 +401,27 @@ export const AppDeviceInboxSchema = z
316
401
  })
317
402
  .strict();
318
403
  export type AppDeviceInbox = z.infer<typeof AppDeviceInboxSchema>;
404
+ /** A request to pair or remove a device of this account, shown to the account's paired devices. */
405
+ export const AppDeviceChangeSchema = z
406
+ .object({
407
+ changeId: Id,
408
+ kind: z.enum(["pair", "revoke"]),
409
+ /** The device to pair or remove, with the name the account gave it. The name may be empty. */
410
+ device: z.object({ id: Id, name: PlainText(80) }).strict(),
411
+ /** Removal only: also end the sign-ins this device approved. Always `false` for a pairing. */
412
+ endSessions: z.boolean(),
413
+ createdAt: DateTime,
414
+ expiresAt: DateTime,
415
+ })
416
+ .strict();
417
+ export type AppDeviceChange = z.infer<typeof AppDeviceChangeSchema>;
418
+ /** The inbox for a device that sent `accept: ["device-changes"]`. Oldest first. */
419
+ export const AppDeviceInboxWithChangesSchema = AppDeviceInboxSchema.extend({
420
+ changes: z.array(AppDeviceChangeSchema).max(APP_APPROVAL_LIMITS.pendingPerAccount),
421
+ /** Pending device changes omitted to fit the response budget. */
422
+ moreChanges: z.number().int().nonnegative().max(APP_APPROVAL_LIMITS.pendingPerAccount),
423
+ }).strict();
424
+ export type AppDeviceInboxWithChanges = z.infer<typeof AppDeviceInboxWithChangesSchema>;
319
425
 
320
426
  /** UTF-8 of this exact JSON array is hashed into an Action's `digest`. Every field the device shows is part of it. */
321
427
  export const appActionDigestMessage = (issuer: string, view: Omit<AppActionView, "digest">): string =>
@@ -356,6 +462,7 @@ export const AppDeviceResponseSchema = z.union([
356
462
  z.object({ state: z.enum(["approved", "denied", "revoked", "updated"]) }).strict(),
357
463
  AppDeviceAccountSchema,
358
464
  AppDeviceInboxSchema,
465
+ AppDeviceInboxWithChangesSchema,
359
466
  AppActionViewSchema,
360
467
  ]);
361
468
  export const AppLoginStartResultSchema = z
@@ -1,6 +1,6 @@
1
1
  import { z } from "zod";
2
2
  import { APP_APPROVAL_LIMITS } from "./app-approval";
3
- import { CapabilityActionReviewSchema, CapabilityAppIdSchema } from "./capabilities";
3
+ import { CapabilityActionReviewDataSchema, CapabilityAppIdSchema } from "./capabilities";
4
4
 
5
5
  export { APPROVAL_COUNTS_PATH, APPROVALS_API_PATH, APPROVALS_PAGE_PATH } from "./approval-paths";
6
6
 
@@ -42,7 +42,7 @@ export const PendingApprovalSchema = z
42
42
  /** What the person reads: the first line names the app and the Action, later lines add `Label: value` details. */
43
43
  sentence: z.string(),
44
44
  /** The owning app's review as the person saw it when Cloud asked. */
45
- review: CapabilityActionReviewSchema.nullable(),
45
+ review: CapabilityActionReviewDataSchema.nullable(),
46
46
  /** Where the request waits. The title is chat content, written by a person or a model, never Cloud's own words. */
47
47
  requester: z.object({ kind: z.literal("chat"), appId: z.string(), title: z.string().nullable(), href: z.string() }).strict(),
48
48
  /** Ways the person may remember this approval, the same the chat card offers. */