@k2b/cloud 0.27.0 → 0.28.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/package.json +2 -2
- package/src/ai/browser-code-contracts.ts +33 -63
- package/src/ai/browser.ts +1 -0
- package/src/ai/chat/blocks.tsx +16 -7
- package/src/ai/chat/live-turn.browser-harness.tsx +6 -3
- package/src/ai/chat/message-actions.tsx +123 -101
- package/src/ai/chat/message-utils.ts +30 -1
- package/src/ai/chat/messages.ts +198 -0
- package/src/ai/chat/presentation.tsx +66 -20
- package/src/ai/chat/tool-groups.ts +1 -1
- package/src/ai/chat/turn-error.ts +21 -0
- package/src/ai/chat/turn-view.tsx +55 -4
- package/src/ai/chat/user-message.tsx +19 -14
- package/src/ai/chat/visual-tools.tsx +1 -1
- package/src/ai/client/controller.ts +23 -6
- package/src/ai/code-mode-skill.ts +21 -25
- package/src/ai/code-runtime-tools.ts +5 -1
- package/src/ai/code-source-contracts.ts +2 -2
- package/src/ai/data-analysis-skill.ts +3 -3
- package/src/ai/default-tools.ts +15 -16
- package/src/ai/executor.ts +135 -59
- package/src/ai/index.ts +2 -0
- package/src/ai/open-tool-calls.ts +87 -0
- package/src/ai/routes.ts +6 -0
- package/src/ai/run-timeout.ts +4 -5
- package/src/ai/runtime.ts +8 -0
- package/src/ai/skill-seeds.ts +4 -4
- package/src/ai/store.ts +46 -14
- package/src/ai/system-prompt.ts +4 -21
- package/src/ai/turn-failure.ts +100 -0
- package/src/ai/turn-policy.ts +2 -1
- package/src/ai/types.ts +28 -1
- package/src/api/admin-outgoing-mail.ts +185 -5
- package/src/browser/FileChooser.tsx +11 -0
- package/src/browser/file-chooser-messages.ts +2 -0
- package/src/cli/admin/index.ts +3 -1
- package/src/cli/admin/notifications.ts +6 -0
- package/src/cli/admin/outgoing-mail.ts +104 -7
- package/src/contracts/outgoing-mail.ts +189 -1
- package/src/services/help/store.ts +2 -1
- package/src/services/index.ts +11 -1
- package/src/services/notifications/batches.ts +139 -79
- package/src/services/notifications/channels.ts +33 -13
- package/src/services/notifications/dispatcher.ts +57 -5
- package/src/services/notifications/email-frame.fixture.html +51 -0
- package/src/services/notifications/{email.ts → email-frame.ts} +12 -35
- package/src/services/notifications/email-mail.ts +101 -0
- package/src/services/notifications/index.ts +49 -36
- package/src/services/notifications/observability.ts +9 -1
- package/src/services/notifications/platform.ts +1 -1
- package/src/services/notifications/runtime.ts +9 -3
- package/src/services/outgoing-mail/admin.ts +84 -0
- package/src/services/outgoing-mail/attachments.ts +135 -0
- package/src/services/outgoing-mail/bulk.ts +11 -0
- package/src/services/outgoing-mail/dispatcher.ts +231 -0
- package/src/services/outgoing-mail/drain.ts +60 -0
- package/src/services/outgoing-mail/enqueue.ts +180 -0
- package/src/services/outgoing-mail/index.ts +103 -10
- package/src/services/outgoing-mail/message.ts +14 -0
- package/src/services/outgoing-mail/messages.ts +431 -0
- package/src/services/outgoing-mail/retention.ts +48 -0
- package/src/services/outgoing-mail/runtime.ts +72 -0
- package/src/services/outgoing-mail/send.ts +130 -0
- package/src/services/outgoing-mail/store.ts +115 -4
- package/src/services/outgoing-mail/sync.ts +39 -0
- package/src/services/outgoing-mail/transport.ts +5 -1
- package/src/services/pdf/markdown.ts +22 -4
- package/src/services/postgres.ts +15 -0
- package/src/services/settings/core-settings.ts +18 -0
- package/src/shared/ai-platform-prompt.ts +48 -4
- package/src/shared/markdown/extensions/links.ts +28 -20
- package/src/shared/markdown/index.ts +14 -5
- package/src/shared/markdown/shared.ts +0 -7
- package/src/ssr/GlobalAnnouncements.island.tsx +1 -1
- package/src/ssr/platform-messages.ts +1 -1
- package/src/styles/effects.css +15 -17
- package/src/styles/tokens.css +2 -0
- package/src/styles/utilities-markdown-editor.css +4 -39
- package/src/styles/utilities-markdown-table.css +14 -17
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@k2b/cloud",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.28.0",
|
|
4
4
|
"description": "Application platform library for independently deployed Hono and SolidJS services behind a dynamic gateway.",
|
|
5
5
|
"license": "AGPL-3.0-or-later",
|
|
6
6
|
"repository": {
|
|
@@ -100,7 +100,7 @@
|
|
|
100
100
|
"@tailwindcss/typography": "0.5.20",
|
|
101
101
|
"@k2b/nessi": "0.15.0",
|
|
102
102
|
"@k2b/ssr": "0.14.0",
|
|
103
|
-
"@k2b/ui": "0.
|
|
103
|
+
"@k2b/ui": "0.18.0",
|
|
104
104
|
"@k2b/stdlib": "0.27.0",
|
|
105
105
|
"@k2b/sync": "7.0.0",
|
|
106
106
|
"@nats-io/jetstream": "3.4.0",
|
|
@@ -10,20 +10,20 @@ export const CODE_RUNTIME_TOOL_NAMES = [
|
|
|
10
10
|
"code_run",
|
|
11
11
|
"code_action",
|
|
12
12
|
"code_inspect",
|
|
13
|
-
"code_interact",
|
|
14
13
|
"code_stop",
|
|
15
14
|
"code_open",
|
|
16
15
|
"code_export",
|
|
17
16
|
"code_present",
|
|
17
|
+
"code_check",
|
|
18
18
|
"code_secret",
|
|
19
19
|
] as const;
|
|
20
20
|
export const CodeRunInput = z
|
|
21
21
|
.object({
|
|
22
|
-
id: id.optional(),
|
|
22
|
+
id: id.optional().describe("Saved script resource to run; an app with an index.html interface opens with code_open instead."),
|
|
23
23
|
resourceId: id
|
|
24
24
|
.optional()
|
|
25
25
|
.describe(
|
|
26
|
-
"Optional data context for one-off code. Requires Manage; uses this resource database and shared files/KV without changing its source.
|
|
26
|
+
"Optional data context for one-off code. Requires Manage; uses this resource database and shared files/KV without changing its source. Runs use real shared and personal data; writes and capability effects keep normal permissions and approvals.",
|
|
27
27
|
),
|
|
28
28
|
version: z
|
|
29
29
|
.number()
|
|
@@ -36,14 +36,12 @@ export const CodeRunInput = z
|
|
|
36
36
|
.min(1)
|
|
37
37
|
.max(1024 * 1024)
|
|
38
38
|
.optional()
|
|
39
|
-
.describe("One-off JavaScript/TypeScript
|
|
39
|
+
.describe("One-off JavaScript/TypeScript script exporting one function. Use code OR a saved resource id; no title or icon needed."),
|
|
40
40
|
inputPaths: z
|
|
41
41
|
.array(z.string().min(1).max(500))
|
|
42
42
|
.max(64)
|
|
43
43
|
.default([])
|
|
44
|
-
.describe(
|
|
45
|
-
"Explicit current-chat input files. Scripts can read them; app test runs expose them only through the simulated picker. User apps never receive chat files.",
|
|
46
|
-
),
|
|
44
|
+
.describe("Explicit current-chat input files; the script reads them from its context files. Apps never receive chat files."),
|
|
47
45
|
})
|
|
48
46
|
.strict()
|
|
49
47
|
.refine((input) => Number(input.id !== undefined) + Number(input.code !== undefined) === 1, "Provide exactly one of id or code")
|
|
@@ -91,66 +89,38 @@ export const CodeInspectInput = z
|
|
|
91
89
|
.max(30000)
|
|
92
90
|
.default(0)
|
|
93
91
|
.describe("Wait up to this duration for background work to finish before returning its real state; never restarts work."),
|
|
94
|
-
nodeId: z.string().min(1).max(80).optional(),
|
|
95
|
-
offset: z.number().int().min(0).default(0),
|
|
96
|
-
limit: z.number().int().min(1).max(100).default(20),
|
|
97
92
|
})
|
|
98
93
|
.strict();
|
|
99
|
-
const
|
|
94
|
+
export const CodeStopInput = z.object({ runId }).strict();
|
|
95
|
+
export const CodeOpenInput = z.object({ id }).strict();
|
|
96
|
+
const AppFilePath = z
|
|
97
|
+
.string()
|
|
98
|
+
.min(1)
|
|
99
|
+
.max(180)
|
|
100
|
+
.regex(/^[a-zA-Z0-9_.-]+(?:\/[a-zA-Z0-9_.-]+)*$/)
|
|
101
|
+
.refine((path) => path.split("/").every((part) => part !== "." && part !== ".."), "Expected a relative path without . or ..");
|
|
102
|
+
export const CodePresentInput = z
|
|
100
103
|
.object({
|
|
101
|
-
id:
|
|
102
|
-
event: z
|
|
103
|
-
.discriminatedUnion("type", [
|
|
104
|
-
z
|
|
105
|
-
.object({
|
|
106
|
-
type: z.literal("change"),
|
|
107
|
-
value: z.union([
|
|
108
|
-
z.string(),
|
|
109
|
-
z.number().finite(),
|
|
110
|
-
z.boolean(),
|
|
111
|
-
z.null(),
|
|
112
|
-
z.array(z.string()),
|
|
113
|
-
z.object({ start: z.iso.date().nullable(), end: z.iso.date().nullable() }).strict(),
|
|
114
|
-
]),
|
|
115
|
-
})
|
|
116
|
-
.strict(),
|
|
117
|
-
z.object({ type: z.literal("select"), key: z.string().nullable() }).strict(),
|
|
118
|
-
z.object({ type: z.literal("view"), value: z.enum(["chart", "table"]) }).strict(),
|
|
119
|
-
z.object({ type: z.literal("refresh") }).strict(),
|
|
120
|
-
z.object({ type: z.literal("request"), request: z.record(z.string(), z.json()) }).strict(),
|
|
121
|
-
])
|
|
104
|
+
id: id
|
|
122
105
|
.optional()
|
|
123
|
-
.describe(
|
|
124
|
-
|
|
125
|
-
)
|
|
126
|
-
answer: z.json().optional().describe("Only for the pending modal ID: the modal answer, or null to cancel. Do not use for controls."),
|
|
127
|
-
})
|
|
128
|
-
.strict();
|
|
129
|
-
const validInteraction = (input: z.infer<typeof CodeInteraction>) => input.event === undefined || input.answer === undefined;
|
|
130
|
-
export const CodeInteractInput = CodeInteraction.partial({ id: true })
|
|
131
|
-
.extend({
|
|
132
|
-
runId,
|
|
133
|
-
// Three normal 15-second callback budgets share the existing 45-second call budget.
|
|
134
|
-
steps: z
|
|
135
|
-
.array(CodeInteraction.refine(validInteraction, "Supply event OR answer"))
|
|
106
|
+
.describe("Saved app with an index.html interface; the card runs it live with its data. Omit title to use the app title."),
|
|
107
|
+
files: z
|
|
108
|
+
.array(z.object({ path: AppFilePath, content: z.string().max(1024 * 1024) }).strict())
|
|
136
109
|
.min(1)
|
|
137
|
-
.max(
|
|
110
|
+
.max(64)
|
|
138
111
|
.optional()
|
|
139
|
-
.describe(
|
|
140
|
-
|
|
141
|
-
),
|
|
112
|
+
.describe("One-off app for this chat: index.html plus optional style.css, app.js and other modules. It has no saved data."),
|
|
113
|
+
title: z.string().trim().min(1).max(120).optional().describe("Card title; required with files."),
|
|
142
114
|
})
|
|
143
115
|
.strict()
|
|
144
|
-
.refine(
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
)
|
|
151
|
-
|
|
152
|
-
export const CodeOpenInput = z.object({ id }).strict();
|
|
153
|
-
export const CodePresentInput = z.object({ runId, title: z.string().trim().min(1).max(120) }).strict();
|
|
116
|
+
.refine((input) => Number(input.id !== undefined) + Number(input.files !== undefined) === 1, "Provide exactly one of id or files")
|
|
117
|
+
.refine((input) => input.id !== undefined || input.title !== undefined, "A one-off app needs a title")
|
|
118
|
+
.refine((input) => !input.files || input.files.some((file) => file.path === "index.html"), "files must contain index.html");
|
|
119
|
+
export const CodeCheckInput = z
|
|
120
|
+
.object({ id: id.optional(), files: CodePresentInput.shape.files })
|
|
121
|
+
.strict()
|
|
122
|
+
.refine((input) => Number(input.id !== undefined) + Number(input.files !== undefined) === 1, "Provide exactly one of id or files")
|
|
123
|
+
.refine((input) => !input.files || input.files.some((file) => file.path === "index.html"), "files must contain index.html");
|
|
154
124
|
export const CodeExportInput = z
|
|
155
125
|
.object({ runId, name: z.string().min(1).max(180).describe("Captured output filename returned by the snapshot.") })
|
|
156
126
|
.strict();
|
|
@@ -179,11 +149,11 @@ export const CodeRuntimeInput = z.discriminatedUnion("operation", [
|
|
|
179
149
|
CodeRunInput.safeExtend({ operation: z.literal("run") }),
|
|
180
150
|
CodeActionInput.safeExtend({ operation: z.literal("action") }),
|
|
181
151
|
CodeInspectInput.extend({ operation: z.literal("inspect") }),
|
|
182
|
-
CodeInteractInput.safeExtend({ operation: z.literal("interact") }),
|
|
183
152
|
CodeStopInput.extend({ operation: z.literal("stop") }),
|
|
184
153
|
CodeOpenInput.extend({ operation: z.literal("open") }),
|
|
185
154
|
CodeSecretInput.extend({ operation: z.literal("secret") }),
|
|
186
|
-
CodePresentInput.
|
|
155
|
+
CodePresentInput.safeExtend({ operation: z.literal("present") }),
|
|
156
|
+
CodeCheckInput.safeExtend({ operation: z.literal("check") }),
|
|
187
157
|
CodeExportInput.extend({ operation: z.literal("export") }),
|
|
188
158
|
]);
|
|
189
159
|
export type CodeRuntimeInput = z.infer<typeof CodeRuntimeInput>;
|
|
@@ -197,12 +167,12 @@ export function parseCodeToolInput(name: string, args: unknown): CodeRuntimeInpu
|
|
|
197
167
|
return { operation: "run", ...CodeRunInput.parse(args) };
|
|
198
168
|
case "code_inspect":
|
|
199
169
|
return { operation: "inspect", ...CodeInspectInput.parse(args) };
|
|
200
|
-
case "code_interact":
|
|
201
|
-
return { operation: "interact", ...CodeInteractInput.parse(args) };
|
|
202
170
|
case "code_stop":
|
|
203
171
|
return { operation: "stop", ...CodeStopInput.parse(args) };
|
|
204
172
|
case "code_open":
|
|
205
173
|
return { operation: "open", ...CodeOpenInput.parse(args) };
|
|
174
|
+
case "code_check":
|
|
175
|
+
return { operation: "check", ...CodeCheckInput.parse(args) };
|
|
206
176
|
case "code_present":
|
|
207
177
|
return { operation: "present", ...CodePresentInput.parse(args) };
|
|
208
178
|
case "code_export":
|
package/src/ai/browser.ts
CHANGED
package/src/ai/chat/blocks.tsx
CHANGED
|
@@ -283,7 +283,7 @@ export function ApprovalBlockView(props: { turnId: string; block: ToolBlock }) {
|
|
|
283
283
|
<div class="mt-4 flex flex-col gap-4 text-xs leading-5 text-secondary">
|
|
284
284
|
<p class="whitespace-pre-wrap">{description()}</p>
|
|
285
285
|
<Show when={inlineReviewDetails().length > 0}>
|
|
286
|
-
<dl class="grid gap-x-5 gap-y-1.5
|
|
286
|
+
<dl class="grid gap-x-5 gap-y-1.5 sm:grid-cols-[max-content_minmax(0,1fr)]">
|
|
287
287
|
<For each={inlineReviewDetails()}>
|
|
288
288
|
{(detail) => (
|
|
289
289
|
<>
|
|
@@ -313,7 +313,12 @@ export function ApprovalBlockView(props: { turnId: string; block: ToolBlock }) {
|
|
|
313
313
|
</pre>
|
|
314
314
|
}
|
|
315
315
|
>
|
|
316
|
-
<div
|
|
316
|
+
<div
|
|
317
|
+
class="-mx-2 max-h-72 overflow-auto px-2 text-xs text-secondary"
|
|
318
|
+
role="region"
|
|
319
|
+
tabIndex={0}
|
|
320
|
+
aria-label={detail.label}
|
|
321
|
+
>
|
|
317
322
|
<MarkdownView class="cloud-ai-approval-detail" markdown={detail.value} headingScale="compact" allowImages={false} />
|
|
318
323
|
</div>
|
|
319
324
|
</Show>
|
|
@@ -323,10 +328,12 @@ export function ApprovalBlockView(props: { turnId: string; block: ToolBlock }) {
|
|
|
323
328
|
</div>
|
|
324
329
|
</Show>
|
|
325
330
|
</div>
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
331
|
+
{/* The decision continues the card below its content: spacing, not a line or a band, sets it apart. */}
|
|
332
|
+
<footer class="flex min-h-12 flex-wrap items-center gap-2 px-4 pb-3" data-ai-approval-footer>
|
|
333
|
+
{/* Present from the start, so screen readers announce progress after the decision buttons leave. */}
|
|
334
|
+
<span class="k2b-sr-only" role="status" aria-live="polite">
|
|
335
|
+
{approval.loading() ? t().submitting : submitted() ? t().submitted : ""}
|
|
336
|
+
</span>
|
|
330
337
|
<Show when={reviewLinks().length > 0}>
|
|
331
338
|
<nav class="flex flex-wrap gap-1" aria-label={t().linksFor({ title: title() })}>
|
|
332
339
|
<For each={reviewLinks()}>
|
|
@@ -339,7 +346,9 @@ export function ApprovalBlockView(props: { turnId: string; block: ToolBlock }) {
|
|
|
339
346
|
</nav>
|
|
340
347
|
</Show>
|
|
341
348
|
<Show when={approval.error()}>
|
|
342
|
-
<p class="text-xs text-red-700 dark:text-red-300">
|
|
349
|
+
<p class="text-xs text-red-700 dark:text-red-300" role="alert">
|
|
350
|
+
{t().approvalFailed}
|
|
351
|
+
</p>
|
|
343
352
|
</Show>
|
|
344
353
|
<div class="ml-auto shrink-0">
|
|
345
354
|
<Show
|
|
@@ -9,12 +9,15 @@ import { AiChatActionsProvider, createAiChatTimeline } from "./presentation";
|
|
|
9
9
|
/**
|
|
10
10
|
* The live chat timeline for a browser test, folded from wire events like the
|
|
11
11
|
* controller does. `window.emit(event)` applies one event,
|
|
12
|
-
* `window.steer(text)` appends a pending steer as the controller does,
|
|
13
|
-
* `window.reconnect(on)` sets whether the stream reconnects
|
|
12
|
+
* `window.steer(text)` appends a pending steer as the controller does,
|
|
13
|
+
* `window.reconnect(on)` sets whether the stream reconnects, and
|
|
14
|
+
* `window.continued` lists the messages that **Continue** sent.
|
|
14
15
|
*/
|
|
15
16
|
const [state, setState] = createStore(emptyProjection());
|
|
16
17
|
const [reconnecting, setReconnecting] = createSignal(false);
|
|
18
|
+
const continued: string[] = [];
|
|
17
19
|
Object.assign(window, {
|
|
20
|
+
continued,
|
|
18
21
|
reconnect: (on: boolean) => setReconnecting(on),
|
|
19
22
|
emit: (event: AiWireEvent) => setState(reconcile(reduceProjection(state, event), { key: "id", merge: true })),
|
|
20
23
|
steer: (text: string) =>
|
|
@@ -29,7 +32,7 @@ root.style.cssText = "display: flex; flex-direction: column; height: 100dvh; max
|
|
|
29
32
|
document.body.append(root);
|
|
30
33
|
render(
|
|
31
34
|
() => (
|
|
32
|
-
<AiChatActionsProvider actions={{}}>
|
|
35
|
+
<AiChatActionsProvider actions={{ onContinueTurn: (message) => void continued.push(message) }}>
|
|
33
36
|
{(() => {
|
|
34
37
|
const items = createAiChatTimeline({
|
|
35
38
|
messages: createMemo(() => visibleMessages(state)),
|