@k2b/cloud 0.27.0 → 0.29.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 (155) hide show
  1. package/package.json +7 -5
  2. package/src/_internal/cli-plugins.ts +18 -0
  3. package/src/_internal/define-app.ts +21 -5
  4. package/src/_internal/registry-validation.ts +30 -1
  5. package/src/_internal/runtime-context.ts +1 -0
  6. package/src/access/PermissionEditor.tsx +11 -4
  7. package/src/ai/assistant-models.ts +25 -4
  8. package/src/ai/audio-tool.ts +3 -10
  9. package/src/ai/browser-code-contracts.ts +33 -63
  10. package/src/ai/browser.ts +1 -0
  11. package/src/ai/capabilities.ts +35 -9
  12. package/src/ai/chat/blocks.tsx +113 -24
  13. package/src/ai/chat/builtin-tools.tsx +3 -1
  14. package/src/ai/chat/composer-adapter.ts +8 -0
  15. package/src/ai/chat/live-turn.browser-harness.tsx +15 -3
  16. package/src/ai/chat/message-actions.tsx +124 -102
  17. package/src/ai/chat/message-utils.ts +57 -6
  18. package/src/ai/chat/messages.ts +214 -0
  19. package/src/ai/chat/presentation.tsx +67 -28
  20. package/src/ai/chat/tool-groups.ts +1 -1
  21. package/src/ai/chat/tool-names.ts +157 -0
  22. package/src/ai/chat/turn-error.ts +21 -0
  23. package/src/ai/chat/turn-layout.ts +12 -0
  24. package/src/ai/chat/turn-view.tsx +69 -10
  25. package/src/ai/chat/user-message.tsx +19 -14
  26. package/src/ai/chat/visual-tools.tsx +1 -1
  27. package/src/ai/chat-tasks.ts +16 -3
  28. package/src/ai/client/controller.ts +23 -6
  29. package/src/ai/code-mode-skill.ts +21 -25
  30. package/src/ai/code-runtime-tools.ts +5 -1
  31. package/src/ai/code-source-contracts.ts +2 -2
  32. package/src/ai/compaction.ts +22 -3
  33. package/src/ai/data-analysis-skill.ts +3 -3
  34. package/src/ai/default-tools.ts +23 -17
  35. package/src/ai/executor.ts +262 -99
  36. package/src/ai/file-media-type.ts +4 -0
  37. package/src/ai/files-store.ts +17 -10
  38. package/src/ai/http.ts +11 -10
  39. package/src/ai/index.ts +3 -0
  40. package/src/ai/memories.ts +2 -0
  41. package/src/ai/memory-learning.ts +59 -5
  42. package/src/ai/memory-workflow-evidence.ts +35 -10
  43. package/src/ai/open-tool-calls.ts +87 -0
  44. package/src/ai/projects-routes.ts +5 -1
  45. package/src/ai/projects.ts +5 -1
  46. package/src/ai/protocol.ts +34 -1
  47. package/src/ai/provider.ts +16 -1
  48. package/src/ai/quota-provider.ts +4 -1
  49. package/src/ai/routes.ts +26 -10
  50. package/src/ai/run-timeout.ts +4 -5
  51. package/src/ai/runtime.ts +8 -0
  52. package/src/ai/settings.ts +54 -17
  53. package/src/ai/skill-seeds.ts +14 -6
  54. package/src/ai/store.ts +102 -34
  55. package/src/ai/structured.ts +2 -1
  56. package/src/ai/system-prompt.ts +9 -22
  57. package/src/ai/transcription.ts +58 -13
  58. package/src/ai/turn-failure.ts +104 -0
  59. package/src/ai/turn-guidance.ts +505 -0
  60. package/src/ai/turn-policy.ts +2 -1
  61. package/src/ai/types.ts +29 -1
  62. package/src/ai/ui.tsx +1 -0
  63. package/src/api/admin-core-settings.ts +16 -1
  64. package/src/api/admin-outgoing-mail.ts +185 -5
  65. package/src/api/me.ts +42 -1
  66. package/src/browser/FileChooser.tsx +82 -295
  67. package/src/browser/FileSaver.tsx +407 -0
  68. package/src/browser/app-badges.ts +16 -0
  69. package/src/browser/choose-files.browser-harness.tsx +22 -6
  70. package/src/browser/choose-files.tsx +23 -36
  71. package/src/browser/file-chooser-messages.ts +66 -0
  72. package/src/browser/file-providers.ts +258 -8
  73. package/src/browser/files.tsx +26 -3
  74. package/src/browser/notifications.ts +37 -1
  75. package/src/browser/provider-folder.tsx +301 -0
  76. package/src/browser/provider-list.ts +39 -0
  77. package/src/browser/save-files.browser-harness.tsx +35 -0
  78. package/src/browser/save-files.tsx +107 -0
  79. package/src/cli/admin/index.ts +3 -1
  80. package/src/cli/admin/notifications.ts +6 -0
  81. package/src/cli/admin/outgoing-mail.ts +104 -7
  82. package/src/contracts/app.ts +10 -0
  83. package/src/contracts/commands.ts +3 -1
  84. package/src/contracts/notification-group.ts +6 -0
  85. package/src/contracts/notification-types.ts +6 -0
  86. package/src/contracts/outgoing-mail.ts +189 -1
  87. package/src/contracts/registry.ts +1 -0
  88. package/src/contracts/user-notifications.ts +74 -0
  89. package/src/server/middleware/request-logger.ts +11 -2
  90. package/src/services/accounts/avatar.ts +5 -2
  91. package/src/services/help/store.ts +2 -1
  92. package/src/services/image-metadata.ts +438 -0
  93. package/src/services/index.ts +12 -1
  94. package/src/services/logging/redaction.ts +50 -0
  95. package/src/services/mandates/index.ts +4 -2
  96. package/src/services/mandates/policy.ts +20 -0
  97. package/src/services/notifications/batches.ts +139 -79
  98. package/src/services/notifications/browser.ts +48 -15
  99. package/src/services/notifications/channels.ts +33 -13
  100. package/src/services/notifications/dispatcher.ts +88 -6
  101. package/src/services/notifications/email-frame.fixture.html +51 -0
  102. package/src/services/notifications/{email.ts → email-frame.ts} +12 -35
  103. package/src/services/notifications/email-mail.ts +101 -0
  104. package/src/services/notifications/index.ts +49 -36
  105. package/src/services/notifications/observability.ts +9 -1
  106. package/src/services/notifications/platform.ts +33 -5
  107. package/src/services/notifications/preview.ts +23 -0
  108. package/src/services/notifications/quiet.ts +153 -0
  109. package/src/services/notifications/runtime.ts +9 -3
  110. package/src/services/notifications/user.ts +6 -0
  111. package/src/services/outgoing-mail/admin.ts +84 -0
  112. package/src/services/outgoing-mail/attachments.ts +135 -0
  113. package/src/services/outgoing-mail/bulk.ts +11 -0
  114. package/src/services/outgoing-mail/dispatcher.ts +231 -0
  115. package/src/services/outgoing-mail/drain.ts +60 -0
  116. package/src/services/outgoing-mail/enqueue.ts +180 -0
  117. package/src/services/outgoing-mail/index.ts +103 -10
  118. package/src/services/outgoing-mail/message.ts +14 -0
  119. package/src/services/outgoing-mail/messages.ts +431 -0
  120. package/src/services/outgoing-mail/retention.ts +48 -0
  121. package/src/services/outgoing-mail/runtime.ts +72 -0
  122. package/src/services/outgoing-mail/send.ts +130 -0
  123. package/src/services/outgoing-mail/store.ts +115 -4
  124. package/src/services/outgoing-mail/sync.ts +39 -0
  125. package/src/services/outgoing-mail/transport.ts +5 -1
  126. package/src/services/pdf/markdown.ts +22 -4
  127. package/src/services/pdf/template-preview.ts +2 -1
  128. package/src/services/postgres.ts +15 -0
  129. package/src/services/settings/app.ts +3 -0
  130. package/src/services/settings/core-settings.ts +18 -0
  131. package/src/services/settings/store.ts +4 -1
  132. package/src/services/weather/geo.ts +6 -2
  133. package/src/shared/ai-platform-prompt.ts +48 -4
  134. package/src/shared/markdown/extensions/links.ts +28 -20
  135. package/src/shared/markdown/index.ts +14 -5
  136. package/src/shared/markdown/shared.ts +0 -7
  137. package/src/shared/redirect.ts +2 -1
  138. package/src/shared/template-rendering.ts +120 -7
  139. package/src/ssr/AppBadge.tsx +30 -0
  140. package/src/ssr/AppLaunchpadPanel.tsx +47 -26
  141. package/src/ssr/GlobalAnnouncements.island.tsx +1 -1
  142. package/src/ssr/IslandError.tsx +33 -0
  143. package/src/ssr/Layout.tsx +3 -0
  144. package/src/ssr/LayoutHelp.tsx +4 -2
  145. package/src/ssr/RailApps.island.tsx +9 -2
  146. package/src/ssr/app-badges.ts +93 -0
  147. package/src/ssr/platform-messages.ts +1 -1
  148. package/src/ssr/rail-messages.ts +4 -0
  149. package/src/ssr/rail-navigation.ts +2 -0
  150. package/src/styles/effects.css +38 -17
  151. package/src/styles/file-chooser.css +25 -0
  152. package/src/styles/tokens.css +2 -0
  153. package/src/styles/utilities-markdown-editor.css +4 -39
  154. package/src/styles/utilities-markdown-table.css +14 -17
  155. package/src/styles/utilities-navigation.css +41 -0
@@ -1,19 +1,40 @@
1
+ import { sql } from "bun";
1
2
  import { type Context, Hono, type MiddlewareHandler } from "hono";
2
- import { validator } from "hono-openapi";
3
+ import { describeRoute, validator } from "hono-openapi";
3
4
  import { z } from "zod";
4
- import { MailAppAccessSchema, MailProfileInputSchema, MailProfileKeySchema } from "../contracts/outgoing-mail";
5
- import { type AuthContext, auth } from "../server";
5
+ import {
6
+ AdminMailRecordSchema,
7
+ MailAppAccessSchema,
8
+ MailProfileInputSchema,
9
+ MailProfileKeySchema,
10
+ MailRetentionSchema,
11
+ MailStatusSchema,
12
+ } from "../contracts/outgoing-mail";
13
+ import { type AuthContext, auth, jsonResponse, requiresAdmin } from "../server";
14
+ import { audit } from "../services/audit";
15
+ import { outgoingMailLog } from "../services/outgoing-mail/admin";
16
+ import { MAIL_RETENTION_KEYS, readMailRetention } from "../services/outgoing-mail/retention";
6
17
  import { type MailAuditContext, OutgoingMailError, outgoingMailStore } from "../services/outgoing-mail/store";
7
18
  import { outgoingMailTest } from "../services/outgoing-mail/test-send";
19
+ import * as settings from "../services/settings";
8
20
 
9
- const validate = <Target extends "param" | "json", Schema extends z.ZodType>(target: Target, schema: Schema) =>
21
+ const retentionInput = MailRetentionSchema.refine((value) => value.recordDays >= value.contentDays, {
22
+ message: "Record retention must be at least content retention.",
23
+ path: ["recordDays"],
24
+ });
25
+
26
+ const validate = <Target extends "param" | "json" | "query", Schema extends z.ZodType>(
27
+ target: Target,
28
+ schema: Schema,
29
+ code: "invalid_profile" | "bad_input" = "invalid_profile",
30
+ ) =>
10
31
  validator(target, schema, (result, c) => {
11
32
  if (!result.success) {
12
33
  const issue = result.error?.[0];
13
34
  const path = issue?.path?.map((part) => (typeof part === "object" && "key" in part ? String(part.key) : String(part))).join(".");
14
35
  return c.json(
15
36
  {
16
- code: "invalid_profile" as const,
37
+ code,
17
38
  message: issue ? `${path ? `${path}: ` : ""}${issue.message}` : "Invalid outgoing mail configuration.",
18
39
  },
19
40
  400,
@@ -24,6 +45,52 @@ const context = (c: Context<AuthContext>): MailAuditContext => ({
24
45
  actor: { userId: c.get("user").id, uid: c.get("user").uid, provider: c.get("user").provider, roles: c.get("user").roles },
25
46
  requestId: c.req.header("x-request-id"),
26
47
  });
48
+ const idParam = z.object({ id: z.uuid() });
49
+ const logQuery = z
50
+ .object({
51
+ app: z.string().min(1).optional(),
52
+ profile: MailProfileKeySchema.optional(),
53
+ status: z
54
+ .union([z.string(), z.array(z.string())])
55
+ .optional()
56
+ .transform((value) =>
57
+ value === undefined ? undefined : typeof value === "string" ? value.split(",") : value.flatMap((part) => part.split(",")),
58
+ )
59
+ .pipe(z.array(MailStatusSchema).max(6).optional()),
60
+ since: z.iso.datetime({ offset: true }).optional(),
61
+ ref: z
62
+ .string()
63
+ .min(1)
64
+ .max(200)
65
+ .optional()
66
+ .transform((value) => {
67
+ if (value === undefined) return undefined;
68
+ const colon = value.indexOf(":");
69
+ return colon < 0 ? { scope: value } : { scope: value.slice(0, colon), id: value.slice(colon + 1) };
70
+ })
71
+ .pipe(z.object({ scope: z.string().min(1).max(200), id: z.string().min(1).max(200).optional() }).optional()),
72
+ recipient: z.string().max(320).optional(),
73
+ cursor: z.string().max(1024).optional(),
74
+ limit: z.coerce.number().int().min(1).max(100).optional(),
75
+ })
76
+ .strict();
77
+ const logPage = z.object({
78
+ items: z.array(AdminMailRecordSchema),
79
+ page: z.int(),
80
+ perPage: z.int(),
81
+ total: z.int(),
82
+ hasNext: z.boolean(),
83
+ nextCursor: z.string().optional(),
84
+ });
85
+ const contentResponse = z.union([
86
+ z.object({ purged: z.literal(true), contentPurgedAt: z.string() }),
87
+ z.object({
88
+ purged: z.literal(false),
89
+ text: z.string().nullable(),
90
+ html: z.string().nullable(),
91
+ headers: z.record(z.string(), z.string()).nullable(),
92
+ }),
93
+ ]);
27
94
  export const createAdminOutgoingMailRoutes = (authenticate: MiddlewareHandler<AuthContext> = auth.requireRole("admin")) =>
28
95
  new Hono<AuthContext>()
29
96
  .use("*", authenticate)
@@ -35,7 +102,120 @@ export const createAdminOutgoingMailRoutes = (authenticate: MiddlewareHandler<Au
35
102
  if (error instanceof OutgoingMailError) return c.json({ error: error.code, code: error.code, message: error.message }, error.status);
36
103
  throw error;
37
104
  })
105
+ .get(
106
+ "/retention",
107
+ describeRoute({
108
+ summary: "Read outgoing mail retention",
109
+ tags: ["Outgoing mail"],
110
+ ...requiresAdmin,
111
+ responses: { 200: jsonResponse(MailRetentionSchema, "Effective retention in days") },
112
+ }),
113
+ async (c) => c.json(await readMailRetention()),
114
+ )
115
+ .put(
116
+ "/retention",
117
+ describeRoute({
118
+ summary: "Update outgoing mail retention",
119
+ tags: ["Outgoing mail"],
120
+ ...requiresAdmin,
121
+ responses: {
122
+ 200: jsonResponse(MailRetentionSchema, "Updated retention in days"),
123
+ 400: jsonResponse(z.object({ code: z.literal("bad_input"), message: z.string() }), "Invalid retention"),
124
+ },
125
+ }),
126
+ validate("json", retentionInput, "bad_input"),
127
+ async (c) => {
128
+ const next = c.req.valid("json");
129
+ const old = await readMailRetention();
130
+ await sql.begin(async (tx) => {
131
+ await settings.set(MAIL_RETENTION_KEYS[0], next.contentDays, tx);
132
+ await settings.set(MAIL_RETENTION_KEYS[1], next.recordDays, tx);
133
+ await audit.record(
134
+ {
135
+ ...context(c),
136
+ action: "outgoing_mail.retention.update",
137
+ outcome: "allowed",
138
+ target: { type: "outgoing_mail_retention" },
139
+ metadata: { old, new: next },
140
+ },
141
+ tx,
142
+ );
143
+ });
144
+ await settings.invalidateSettingsCache(MAIL_RETENTION_KEYS);
145
+ return c.json(next);
146
+ },
147
+ )
148
+ .get(
149
+ "/messages",
150
+ describeRoute({
151
+ summary: "List outgoing mail metadata",
152
+ tags: ["Outgoing mail"],
153
+ ...requiresAdmin,
154
+ responses: { 200: jsonResponse(logPage, "Send log") },
155
+ }),
156
+ validate("query", logQuery, "bad_input"),
157
+ async (c) => {
158
+ const { cursor, limit, ...filter } = c.req.valid("query");
159
+ return c.json(await outgoingMailLog.list(filter, { cursor, perPage: limit }));
160
+ },
161
+ )
162
+ .get(
163
+ "/messages/:id",
164
+ describeRoute({
165
+ summary: "Read outgoing mail metadata",
166
+ tags: ["Outgoing mail"],
167
+ ...requiresAdmin,
168
+ responses: { 200: jsonResponse(AdminMailRecordSchema, "Message metadata") },
169
+ }),
170
+ validate("param", idParam, "bad_input"),
171
+ async (c) => c.json(await outgoingMailLog.get(c.req.valid("param").id)),
172
+ )
173
+ .get(
174
+ "/messages/:id/content",
175
+ describeRoute({
176
+ summary: "Read outgoing mail content with audit",
177
+ tags: ["Outgoing mail"],
178
+ ...requiresAdmin,
179
+ responses: { 200: jsonResponse(contentResponse, "Message content or purge marker") },
180
+ }),
181
+ validate("param", idParam, "bad_input"),
182
+ async (c) => c.json(await outgoingMailLog.content(c.req.valid("param").id, context(c))),
183
+ )
184
+ .post(
185
+ "/messages/:id/cancel",
186
+ describeRoute({
187
+ summary: "Cancel queued outgoing mail",
188
+ tags: ["Outgoing mail"],
189
+ ...requiresAdmin,
190
+ responses: {
191
+ 200: jsonResponse(AdminMailRecordSchema, "Cancelled message"),
192
+ 409: jsonResponse(
193
+ z.object({ code: z.literal("message_not_queued"), error: z.literal("message_not_queued"), message: z.string() }),
194
+ "Only queued mail can be cancelled",
195
+ ),
196
+ },
197
+ }),
198
+ validate("param", idParam, "bad_input"),
199
+ async (c) => c.json(await outgoingMailLog.cancel(c.req.valid("param").id, context(c))),
200
+ )
38
201
  .get("/profiles", async (c) => c.json({ items: await outgoingMailStore.list() }))
202
+ .post(
203
+ "/batches/:batchId/cancel",
204
+ describeRoute({
205
+ summary: "Cancel queued outgoing mail in a batch",
206
+ tags: ["Outgoing mail"],
207
+ ...requiresAdmin,
208
+ responses: {
209
+ 200: jsonResponse(z.object({ batchId: z.uuid(), cancelled: z.int().nonnegative() }), "Cancelled queued messages"),
210
+ 404: jsonResponse(
211
+ z.object({ code: z.literal("batch_unknown"), error: z.literal("batch_unknown"), message: z.string() }),
212
+ "Unknown batch",
213
+ ),
214
+ },
215
+ }),
216
+ validate("param", z.object({ batchId: z.uuid() }), "bad_input"),
217
+ async (c) => c.json(await outgoingMailLog.cancelBatch(c.req.valid("param").batchId, context(c))),
218
+ )
39
219
  .get("/profiles/:key", async (c) => c.json(await outgoingMailStore.get(c.req.param("key"))))
40
220
  .put(
41
221
  "/profiles/:key",
package/src/api/me.ts CHANGED
@@ -23,10 +23,12 @@ import {
23
23
  ListWebAuthnPasskeysResponseSchema,
24
24
  MessageResponseSchema,
25
25
  NotificationDeliveryStatusSchema,
26
+ NotificationQuietSettingsSchema,
26
27
  RegisterBrowserNotificationEndpointSchema,
27
28
  ServiceAccountCredentialSchema,
28
29
  UpdateAvatarResponseSchema,
29
30
  UpdateAvatarSchema,
31
+ UpdateNotificationQuietSettingsSchema,
30
32
  UpdateProfileSchema,
31
33
  UpdateUserNotificationPreferenceSchema,
32
34
  UserNotificationHistoryResponseSchema,
@@ -36,7 +38,7 @@ import {
36
38
  WebAuthnPasskeySchema,
37
39
  } from "../contracts";
38
40
  import { CapabilityAppIdSchema } from "../contracts/capabilities";
39
- import { type AuthContext, auth, getLocale, jsonResponse, rateLimit, requiresAuth, respond, v } from "../server";
41
+ import { type AuthContext, auth, getLocale, getTimeZone, jsonResponse, rateLimit, requiresAuth, respond, v } from "../server";
40
42
  import { APP_SESSION_FORBIDDEN } from "../server/middleware/auth";
41
43
  import {
42
44
  accountLifecycle,
@@ -363,6 +365,44 @@ const app = new Hono<AuthContext>()
363
365
  ),
364
366
  )
365
367
 
368
+ .get(
369
+ "/notifications/quiet",
370
+ describeRoute({
371
+ tags: ["Me"],
372
+ summary: "Get current user do not disturb and quiet hours",
373
+ description:
374
+ "Return do not disturb, the weekly quiet hours, and whether browser notifications from every app are held back right now.",
375
+ ...requiresAuth,
376
+ responses: {
377
+ 200: jsonResponse(NotificationQuietSettingsSchema, "Quiet settings and current state"),
378
+ 401: jsonResponse(ErrorResponseSchema, "Authentication required"),
379
+ },
380
+ }),
381
+ async (c) => respond(c, async () => ok(await notifications.user.quiet.get(c.get("user").id, getTimeZone(c)))),
382
+ )
383
+
384
+ .patch(
385
+ "/notifications/quiet",
386
+ describeRoute({
387
+ tags: ["Me"],
388
+ summary: "Update current user do not disturb and quiet hours",
389
+ description:
390
+ "Pause browser notifications until a future instant at most 366 days ahead (null resumes them) or replace the weekly quiet hours. Omitted fields stay unchanged.",
391
+ ...requiresAuth,
392
+ responses: {
393
+ 200: jsonResponse(NotificationQuietSettingsSchema, "Updated quiet settings and current state"),
394
+ 400: jsonResponse(ErrorResponseSchema, "Invalid quiet settings"),
395
+ 401: jsonResponse(ErrorResponseSchema, "Authentication required"),
396
+ },
397
+ }),
398
+ v("json", UpdateNotificationQuietSettingsSchema),
399
+ async (c) =>
400
+ respond(
401
+ c,
402
+ notifications.user.quiet.update({ userId: c.get("user").id, update: c.req.valid("json"), fallbackTimeZone: getTimeZone(c) }),
403
+ ),
404
+ )
405
+
366
406
  .get(
367
407
  "/notifications/history",
368
408
  describeRoute({
@@ -438,6 +478,7 @@ const app = new Hono<AuthContext>()
438
478
  200: jsonResponse(UpdateAvatarResponseSchema, "Avatar updated"),
439
479
  400: jsonResponse(ErrorResponseSchema, "Failed to update avatar"),
440
480
  401: jsonResponse(ErrorResponseSchema, "Authentication required"),
481
+ 422: jsonResponse(ErrorResponseSchema, "Malformed image container or EXIF"),
441
482
  },
442
483
  }),
443
484
  v("json", UpdateAvatarSchema),