@k2b/cloud 0.38.0 → 0.40.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/README.md +8 -0
- package/package.json +12 -5
- package/scripts/build-canvas-workers.ts +109 -29
- package/scripts/build.ts +3 -5
- package/src/_internal/app-favicon.ts +4 -2
- package/src/_internal/app-path.ts +25 -0
- package/src/_internal/app-presentation.ts +4 -0
- package/src/_internal/canvas-worker.ts +5 -3
- package/src/_internal/capabilities.ts +72 -48
- package/src/_internal/capability-reviews.ts +81 -0
- package/src/_internal/capability-schemas.ts +29 -0
- package/src/_internal/capability-sentences.ts +216 -1
- package/src/_internal/capability-streams.ts +7 -0
- package/src/_internal/define-app.ts +63 -16
- package/src/_internal/font-preloads.ts +2 -1
- package/src/_internal/panel-cookie.ts +39 -0
- package/src/_internal/registry-validation.ts +21 -26
- package/src/_internal/runtime-context.ts +1 -0
- package/src/_internal/runtime-watcher.ts +6 -0
- package/src/_internal/static-assets.ts +52 -3
- package/src/_internal/status-preserving-ssr.ts +2 -0
- package/src/access/PermissionEditor.browser-harness.tsx +57 -38
- package/src/access/PermissionEditor.tsx +32 -7
- package/src/access/ResourceApiKeys.tsx +5 -1
- package/src/account/EntitySearch.tsx +1 -1
- package/src/ai/action-review.ts +101 -0
- package/src/ai/approval-routes.ts +5 -2
- package/src/ai/approvals.ts +139 -28
- package/src/ai/background-review.ts +72 -0
- package/src/ai/browser.ts +10 -0
- package/src/ai/capabilities.ts +29 -11
- package/src/ai/capability-execution.ts +76 -43
- package/src/ai/capability-result.ts +95 -0
- package/src/ai/chat/action-card.ts +398 -0
- package/src/ai/chat/action-messages.ts +129 -0
- package/src/ai/chat/action-question.browser-harness.tsx +245 -0
- package/src/ai/chat/action-question.fixtures.ts +733 -0
- package/src/ai/chat/action-question.tsx +669 -0
- package/src/ai/chat/action-sentences.ts +1 -44
- package/src/ai/chat/blocks.tsx +19 -25
- package/src/ai/chat/message-actions.tsx +6 -0
- package/src/ai/chat/tool-action.tsx +481 -0
- package/src/ai/chat/turn-view.tsx +45 -163
- package/src/ai/chat/view-block.tsx +22 -7
- package/src/ai/chat-task-contracts.ts +31 -3
- package/src/ai/chat-task-reasons-migrate.ts +75 -0
- package/src/ai/chat-task-reasons.ts +92 -0
- package/src/ai/chat-tasks.ts +438 -78
- package/src/ai/client/controller.ts +59 -0
- package/src/ai/default-tools.ts +9 -1
- package/src/ai/executor.ts +190 -39
- package/src/ai/index.ts +14 -0
- package/src/ai/migrate.ts +39 -4
- package/src/ai/projects-routes.ts +3 -0
- package/src/ai/protocol.ts +14 -2
- package/src/ai/recent-resources.ts +240 -0
- package/src/ai/recent-sent-refs.ts +38 -0
- package/src/ai/request-context.ts +7 -1
- package/src/ai/routes.ts +172 -2
- package/src/ai/runtime.ts +13 -27
- package/src/ai/skills-routes.ts +3 -0
- package/src/ai/skills.ts +10 -1
- package/src/ai/store.ts +232 -27
- package/src/ai/timeline.ts +9 -1
- package/src/ai/tool-audit.ts +8 -0
- package/src/ai/tool-wording.ts +67 -0
- package/src/ai/turn-abort.ts +33 -0
- package/src/ai/types.ts +40 -4
- package/src/api/accounts-entities.ts +2 -1
- package/src/api/admin-ai-projects.ts +2 -0
- package/src/api/admin-ai-skills.ts +2 -0
- package/src/api/admin-ai-usage.ts +2 -0
- package/src/api/admin-core-settings.ts +190 -139
- package/src/api/admin-geoip.ts +80 -0
- package/src/api/admin-identity.ts +3 -0
- package/src/api/admin-lifecycle.ts +3 -0
- package/src/api/admin-outgoing-mail.ts +6 -2
- package/src/api/app-approval.ts +54 -6
- package/src/api/approvals-loader.ts +23 -1
- package/src/api/approvals.ts +3 -1
- package/src/api/auth.ts +25 -6
- package/src/api/capabilities.ts +44 -11
- package/src/api/capability-streams.ts +12 -0
- package/src/api/help.ts +4 -28
- package/src/api/index.ts +7 -1
- package/src/api/lasting-access-messages.ts +17 -0
- package/src/api/mcp.ts +3 -0
- package/src/api/me-panel-consent.ts +71 -0
- package/src/api/me.ts +80 -33
- package/src/api/search.ts +3 -1
- package/src/api/sync-ops.ts +2 -0
- package/src/api/widgets.ts +3 -0
- package/src/approvals/Approvals.tsx +180 -238
- package/src/approvals/messages.ts +12 -24
- package/src/browser/CloudResourceSearch.browser-harness.tsx +10 -2
- package/src/browser/CloudResourceSearch.tsx +10 -6
- package/src/browser/app-approval.ts +60 -6
- package/src/browser/commands.ts +22 -6
- package/src/browser/composer-catalog.ts +145 -0
- package/src/browser/composer-insert-messages.ts +31 -0
- package/src/browser/composer-insert.ts +160 -0
- package/src/browser/composer-open-messages.ts +17 -0
- package/src/browser/composer-open.ts +110 -0
- package/src/browser/discoverable-command.ts +6 -0
- package/src/browser/hints.ts +6 -0
- package/src/browser/live-connection.ts +26 -2
- package/src/browser/navigation-apps.ts +7 -0
- package/src/browser/panel-context-bridge.ts +30 -0
- package/src/browser/panel-context-receiver.ts +53 -0
- package/src/browser/panel-menu.tsx +56 -0
- package/src/browser/panel-messages.ts +35 -0
- package/src/browser/panel-shortcuts.ts +31 -0
- package/src/browser/panel-slot.ts +756 -0
- package/src/browser/panel.ts +65 -0
- package/src/browser/resource-picker.tsx +12 -1
- package/src/browser/search-commands.ts +6 -3
- package/src/capabilities/client.ts +8 -2
- package/src/capabilities/reviews.ts +205 -0
- package/src/capabilities/server.ts +10 -3
- package/src/capabilities/testing.ts +47 -0
- package/src/capabilities/types.ts +2 -2
- package/src/cli/account.ts +13 -3
- package/src/cli/admin/geoip.ts +115 -0
- package/src/cli/admin/index.ts +3 -0
- package/src/contracts/app-approval.ts +159 -12
- package/src/contracts/app.ts +14 -0
- package/src/contracts/approvals.ts +2 -2
- package/src/contracts/capabilities.ts +410 -18
- package/src/contracts/geoip.ts +42 -0
- package/src/contracts/index.ts +3 -0
- package/src/contracts/panel-context-plain.ts +99 -0
- package/src/contracts/panel-context.ts +93 -0
- package/src/contracts/registry.ts +3 -1
- package/src/contracts/seen-hints.ts +35 -0
- package/src/contracts/shared.ts +2 -0
- package/src/server/client-ip.ts +5 -0
- package/src/server/help-payload.ts +38 -0
- package/src/server/middleware/auth.ts +11 -1
- package/src/server/middleware/middleware.ts +9 -0
- package/src/server/middleware/no-transform.ts +19 -0
- package/src/server/middleware/rate-limit.ts +4 -6
- package/src/server/request-context/address.ts +45 -0
- package/src/server/request-context/geoip.ts +380 -0
- package/src/server/request-context/index.ts +33 -0
- package/src/server/request-context/user-agent.ts +95 -0
- package/src/server/services/access.ts +5 -1
- package/src/services/accounts/posix.ts +2 -1
- package/src/services/app-approval.ts +533 -90
- package/src/services/approvals/device.ts +9 -5
- package/src/services/approvals/index.ts +6 -5
- package/src/services/approvals/list.ts +26 -5
- package/src/services/auth-flows/notification-sender.ts +3 -0
- package/src/services/geoip/index.ts +164 -0
- package/src/services/geoip/store.ts +30 -0
- package/src/services/geoip/upload.ts +150 -0
- package/src/services/identity/routes.ts +5 -1
- package/src/services/index.ts +32 -3
- package/src/services/known-browsers.ts +312 -0
- package/src/services/login-address-store.ts +51 -0
- package/src/services/login-context.ts +118 -0
- package/src/services/mandates/index.ts +3 -0
- package/src/services/mandates/policy.ts +15 -3
- package/src/services/notifications/browser.ts +16 -5
- package/src/services/notifications/email-frame.ts +1 -1
- package/src/services/oauth-tokens.ts +16 -2
- package/src/services/panel-context-consent.ts +58 -0
- package/src/services/pdf/image-protocol.ts +135 -0
- package/src/services/pdf/image-variants.ts +124 -0
- package/src/services/pdf/image-worker.ts +31 -0
- package/src/services/pdf/image.ts +109 -0
- package/src/services/pdf/index.ts +9 -0
- package/src/services/service-account-credentials.ts +21 -0
- package/src/services/session/index.ts +97 -16
- package/src/services/session/recent.ts +13 -0
- package/src/services/settings/api.ts +11 -6
- package/src/services/settings/app.ts +5 -4
- package/src/services/settings/core-settings.ts +4 -3
- package/src/services/settings/index.ts +19 -17
- package/src/services/settings/namespace.ts +2 -1
- package/src/services/settings/store.ts +83 -11
- package/src/services/webauthn.ts +12 -0
- package/src/shared/ai-platform-prompt.ts +1 -1
- package/src/shared/app-presentation.ts +1 -0
- package/src/shared/email-html.ts +2 -0
- package/src/shared/help.ts +3 -0
- package/src/shared/redirect.ts +12 -0
- package/src/shared-text/cursor.ts +22 -0
- package/src/shared-text/documents.ts +142 -0
- package/src/shared-text/events.ts +43 -0
- package/src/shared-text/index.ts +12 -0
- package/src/shared-text/log.ts +48 -0
- package/src/shared-text/text-diff.ts +23 -0
- package/src/shared-text/types.ts +41 -0
- package/src/ssr/AdminLayout.tsx +2 -0
- package/src/ssr/AppLaunchpadPanel.tsx +2 -2
- package/src/ssr/CloudShell.island.tsx +95 -10
- package/src/ssr/Layout.tsx +66 -10
- package/src/ssr/LayoutHeader.tsx +15 -9
- package/src/ssr/LayoutHelp.browser-harness.tsx +2 -0
- package/src/ssr/LayoutHelp.tsx +435 -274
- package/src/ssr/LayoutHelpPanel.browser-harness.tsx +136 -0
- package/src/ssr/LayoutHelpTrigger.tsx +27 -2
- package/src/ssr/LayoutRail.tsx +2 -2
- package/src/ssr/LayoutTabBar.tsx +29 -0
- package/src/ssr/MobileNavigation.tsx +2 -2
- package/src/ssr/PanelLayout.tsx +35 -0
- package/src/ssr/PanelRuntime.island.tsx +94 -0
- package/src/ssr/PanelSlot.tsx +158 -0
- package/src/ssr/PanelTrigger.tsx +49 -0
- package/src/ssr/ShellRuntime.island.tsx +69 -5
- package/src/ssr/app-appearance.ts +3 -2
- package/src/ssr/app-color.ts +105 -0
- package/src/ssr/app-launchpad.tsx +18 -1
- package/src/ssr/app-navigation.ts +28 -0
- package/src/ssr/compact-shell.baseline.json +5270 -0
- package/src/ssr/compact-shell.browser-harness.ts +439 -0
- package/src/ssr/dialog-app-colors.browser-harness.tsx +43 -0
- package/src/ssr/help-messages.ts +4 -0
- package/src/ssr/help.ts +20 -0
- package/src/ssr/hints.ts +25 -0
- package/src/ssr/index.ts +5 -0
- package/src/ssr/islands/index.ts +4 -3
- package/src/ssr/layout-context.ts +2 -0
- package/src/ssr/layout-help-article.server.ts +50 -0
- package/src/ssr/layout-help-start.ts +33 -7
- package/src/ssr/panel-consent.ts +29 -0
- package/src/ssr/panel-context.ts +10 -0
- package/src/ssr/panel-slot.browser-harness.tsx +159 -0
- package/src/ssr/platform-messages.ts +48 -0
- package/src/ssr/rail-navigation.ts +44 -0
- package/src/ssr/shell-data.tsx +5 -0
- package/src/ssr/shell-shortcuts.ts +13 -2
- package/src/styles/effects.css +201 -11
- package/src/styles/file-chooser.css +2 -1
- package/src/styles/global.css +314 -63
- package/src/styles/panel.css +416 -0
- package/src/styles/resource-search.css +6 -2
- package/src/styles/tokens.css +36 -13
- package/src/styles/utilities-buttons.css +3 -2
- package/src/styles/utilities-data.css +2 -1
- package/src/styles/utilities-feedback.css +2 -2
- package/src/styles/utilities-layout.css +3 -13
- package/src/styles/utilities-markdown-editor.css +6 -6
- package/src/styles/utilities-navigation.css +22 -43
|
@@ -30,7 +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"] 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;
|
|
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();
|
|
34
50
|
|
|
35
51
|
const Id = z.string().uuid();
|
|
36
52
|
const Secret = z.string().regex(/^[A-Za-z0-9_-]{42}[AEIMQUYcgkosw048]$/);
|
|
@@ -67,21 +83,42 @@ export const AppLoginStartSchema = z
|
|
|
67
83
|
export const AppLoginReferenceSchema = z.object({ requestId: Id, browserSecret: Secret }).strict();
|
|
68
84
|
export const AppDeviceMutationSchema = z.discriminatedUnion("operation", [
|
|
69
85
|
z.object({ operation: z.literal("rename"), deviceId: Id, name: z.string().trim().min(1).max(80) }).strict(),
|
|
70
|
-
|
|
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(),
|
|
71
91
|
]);
|
|
72
92
|
export const AppDeviceCommandSchema = z.discriminatedUnion("operation", [
|
|
73
|
-
z.object({ operation: z.literal("pending") }).strict(),
|
|
93
|
+
z.object({ operation: z.literal("pending"), accept: acceptList(APP_APPROVAL_PENDING_ACCEPT) }).strict(),
|
|
74
94
|
z.object({ operation: z.literal("revoke") }).strict(),
|
|
75
95
|
/** Reads the signing device's own account and device record, e.g. to show which account it signs in. */
|
|
76
96
|
z.object({ operation: z.literal("account") }).strict(),
|
|
77
97
|
/** Stores the authenticator's opaque push token for sign-in wake-ups on this device. */
|
|
78
98
|
z.object({ operation: z.literal("push"), token: z.string().regex(/^[A-Za-z0-9_-]{43}$/) }).strict(),
|
|
79
99
|
/** Pending sign-ins and the newest open Action approvals of the signing device's account in one call. */
|
|
80
|
-
z
|
|
100
|
+
z
|
|
101
|
+
.object({
|
|
102
|
+
operation: z.literal("inbox"),
|
|
103
|
+
/** Optional inbox parts this device shows; only send values the Cloud lists in `/info` `features`. */
|
|
104
|
+
accept: acceptList(APP_APPROVAL_ACCEPT),
|
|
105
|
+
})
|
|
106
|
+
.strict(),
|
|
81
107
|
/** One open Action approval as the device shows it, with the digest the device recomputes before it decides. */
|
|
82
108
|
z.object({ operation: z.literal("action"), actionId: Id }).strict(),
|
|
83
109
|
/** Decides an Action approval. `digest` binds the decision to exactly the view the person saw. */
|
|
84
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(),
|
|
85
122
|
z
|
|
86
123
|
.object({
|
|
87
124
|
operation: z.literal("decide"),
|
|
@@ -121,6 +158,8 @@ export const appDeviceProofMessage = (proof: AppDeviceProof): string => {
|
|
|
121
158
|
c.operation,
|
|
122
159
|
...(c.operation === "decide" ? [c.requestId, c.challenge, c.comparison, c.decision] : []),
|
|
123
160
|
...(c.operation === "push" ? [c.token] : []),
|
|
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] : []),
|
|
124
163
|
...(c.operation === "action" ? [c.actionId] : []),
|
|
125
164
|
...(c.operation === "act" ? [c.actionId, c.digest, c.decision] : []),
|
|
126
165
|
]);
|
|
@@ -151,8 +190,79 @@ export const AppDeviceViewSchema = z
|
|
|
151
190
|
assisted: z.boolean(),
|
|
152
191
|
})
|
|
153
192
|
.strict();
|
|
193
|
+
/** Plain text on one line: no control, format or bidirectional characters, so an app cannot fake Cloud's own frame. */
|
|
194
|
+
const PlainText = (max: number) =>
|
|
195
|
+
z
|
|
196
|
+
.string()
|
|
197
|
+
.refine((value) => Array.from(value).length <= max, `Must contain at most ${max} code points`)
|
|
198
|
+
.regex(/^[^\p{Cc}\p{Cf}\p{Zl}\p{Zp}]*$/u);
|
|
199
|
+
export const AppTerminalLoginSchema = z
|
|
200
|
+
.object({
|
|
201
|
+
client: z
|
|
202
|
+
.object({
|
|
203
|
+
// Bound encoded bytes including escapes, so five complete consent rows fit the response budget.
|
|
204
|
+
id: PlainText(256)
|
|
205
|
+
.min(1)
|
|
206
|
+
.refine((value) => new TextEncoder().encode(JSON.stringify(value)).length <= 258),
|
|
207
|
+
name: PlainText(APP_ACTION_LIMITS.nameChars).min(1),
|
|
208
|
+
})
|
|
209
|
+
.strict(),
|
|
210
|
+
scopes: z
|
|
211
|
+
.array(z.string().regex(/^[a-z][a-z0-9_:.-]{0,39}$/))
|
|
212
|
+
.min(1)
|
|
213
|
+
.max(16),
|
|
214
|
+
lifetimeSeconds: z.number().int().positive(),
|
|
215
|
+
})
|
|
216
|
+
.strict();
|
|
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
|
+
|
|
154
256
|
export const AppPendingLoginSchema = z
|
|
155
|
-
.object({
|
|
257
|
+
.object({
|
|
258
|
+
requestId: Id,
|
|
259
|
+
challenge: Secret,
|
|
260
|
+
comparison: Comparison,
|
|
261
|
+
createdAt: DateTime,
|
|
262
|
+
expiresAt: DateTime,
|
|
263
|
+
terminal: AppTerminalLoginSchema.optional(),
|
|
264
|
+
context: AppLoginContextSchema.optional(),
|
|
265
|
+
})
|
|
156
266
|
.strict();
|
|
157
267
|
export const AppPairingPayloadSchema = z
|
|
158
268
|
.object({ protocol: z.literal(APP_APPROVAL_PROTOCOL), issuer: z.string().url(), pairingId: Id, secret: Secret, expiresAt: DateTime })
|
|
@@ -205,8 +315,29 @@ export const AppPairingClaimResultSchema = z.object({ deviceId: Id, comparison:
|
|
|
205
315
|
export const AppPairingResultSchema = z
|
|
206
316
|
.object({ state: z.enum(["pending", "claimed", "confirmed", "cancelled"]), deviceId: Id.nullable(), comparison: Comparison.nullable() })
|
|
207
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>;
|
|
208
323
|
/** Initiating Cloud session only; never returned to an unpaired device. */
|
|
209
|
-
export const AppPairingInspectionSchema = AppPairingResultSchema.extend({
|
|
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
|
+
]);
|
|
210
341
|
/** Answer to the `account` command: only the signing device's own account, never secrets or other users. */
|
|
211
342
|
export const AppDeviceAccountSchema = z
|
|
212
343
|
.object({
|
|
@@ -215,12 +346,6 @@ export const AppDeviceAccountSchema = z
|
|
|
215
346
|
})
|
|
216
347
|
.strict();
|
|
217
348
|
export type AppDeviceAccount = z.infer<typeof AppDeviceAccountSchema>;
|
|
218
|
-
/** Plain text on one line: no control, format or bidirectional characters, so an app cannot fake Cloud's own frame. */
|
|
219
|
-
const PlainText = (max: number) =>
|
|
220
|
-
z
|
|
221
|
-
.string()
|
|
222
|
-
.refine((value) => Array.from(value).length <= max, `Must contain at most ${max} code points`)
|
|
223
|
-
.regex(/^[^\p{Cc}\p{Cf}\p{Zl}\p{Zp}]*$/u);
|
|
224
349
|
const ActionApp = z
|
|
225
350
|
.object({ id: PlainText(APP_ACTION_LIMITS.nameChars).min(1), name: PlainText(APP_ACTION_LIMITS.nameChars).min(1) })
|
|
226
351
|
.strict();
|
|
@@ -276,6 +401,27 @@ export const AppDeviceInboxSchema = z
|
|
|
276
401
|
})
|
|
277
402
|
.strict();
|
|
278
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>;
|
|
279
425
|
|
|
280
426
|
/** UTF-8 of this exact JSON array is hashed into an Action's `digest`. Every field the device shows is part of it. */
|
|
281
427
|
export const appActionDigestMessage = (issuer: string, view: Omit<AppActionView, "digest">): string =>
|
|
@@ -316,6 +462,7 @@ export const AppDeviceResponseSchema = z.union([
|
|
|
316
462
|
z.object({ state: z.enum(["approved", "denied", "revoked", "updated"]) }).strict(),
|
|
317
463
|
AppDeviceAccountSchema,
|
|
318
464
|
AppDeviceInboxSchema,
|
|
465
|
+
AppDeviceInboxWithChangesSchema,
|
|
319
466
|
AppActionViewSchema,
|
|
320
467
|
]);
|
|
321
468
|
export const AppLoginStartResultSchema = z
|
package/src/contracts/app.ts
CHANGED
|
@@ -58,6 +58,7 @@ export type AppAdminNavigationGroup = {
|
|
|
58
58
|
|
|
59
59
|
export type AppPresentationTranslation = {
|
|
60
60
|
name?: string;
|
|
61
|
+
panelLabel?: string;
|
|
61
62
|
description?: string;
|
|
62
63
|
/** Group labels keyed by `adminNav[].id`. */
|
|
63
64
|
adminGroups?: Readonly<Record<string, string>>;
|
|
@@ -107,6 +108,18 @@ export type AppPwaPart = {
|
|
|
107
108
|
requiresRoles?: Role[];
|
|
108
109
|
};
|
|
109
110
|
|
|
111
|
+
/** One same-origin panel page contributed by an application. Routes and services still authorize. */
|
|
112
|
+
export type AppPanel = {
|
|
113
|
+
/** GET page below the app's routes, rendered with PanelLayout. */
|
|
114
|
+
href: string;
|
|
115
|
+
/** Panel name in the app's base locale; translated with presentation.translations[locale].panelLabel. */
|
|
116
|
+
label: string;
|
|
117
|
+
/** Icon class, for example "ti ti-sparkles". */
|
|
118
|
+
icon: string;
|
|
119
|
+
/** Coarse visibility like nav.requiresRoles; the slot grants no access. */
|
|
120
|
+
requiresRoles?: Role[];
|
|
121
|
+
};
|
|
122
|
+
|
|
110
123
|
export type AppMeta = {
|
|
111
124
|
platformPermissions?: readonly PlatformPermission[];
|
|
112
125
|
id: string;
|
|
@@ -142,6 +155,7 @@ export type AppMeta = {
|
|
|
142
155
|
searchLinks?: readonly AppSearchLink[];
|
|
143
156
|
/** The app's pages in the installable mobile app (preview), at `href` = `/pwa/<id>`. */
|
|
144
157
|
pwa?: AppPwaPart;
|
|
158
|
+
panel?: AppPanel;
|
|
145
159
|
/**
|
|
146
160
|
* Dashboard widget endpoints this app exposes. Each entry references an
|
|
147
161
|
* HTTP endpoint that returns a `WidgetResponse` (see `contracts/widgets.ts`).
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { z } from "zod";
|
|
2
2
|
import { APP_APPROVAL_LIMITS } from "./app-approval";
|
|
3
|
-
import {
|
|
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:
|
|
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. */
|