@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.
- package/README.md +8 -0
- package/package.json +11 -4
- package/src/_internal/app-favicon.ts +4 -2
- 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 +217 -1
- package/src/_internal/capability-streams.ts +7 -0
- package/src/_internal/define-app.ts +49 -18
- package/src/_internal/font-preloads.ts +2 -1
- package/src/_internal/heartbeat.ts +203 -14
- package/src/_internal/registry.ts +28 -1
- package/src/_internal/runtime-watcher.ts +6 -0
- package/src/_internal/setup-lock.ts +103 -0
- package/src/_internal/static-assets.ts +52 -3
- package/src/_internal/status-preserving-ssr.ts +2 -0
- package/src/access/PermissionEditor.tsx +2 -2
- package/src/account/EntitySearch.tsx +1 -1
- package/src/ai/action-review.ts +101 -0
- package/src/ai/background-review.ts +72 -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 +464 -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 +680 -0
- package/src/ai/chat/action-text.ts +288 -0
- package/src/ai/chat/blocks.tsx +19 -25
- package/src/ai/chat/message-actions.tsx +6 -0
- package/src/ai/chat/tool-action-words.ts +31 -0
- package/src/ai/chat/tool-action.tsx +456 -0
- package/src/ai/chat/turn-view.tsx +10 -160
- package/src/ai/chat/view-block.tsx +15 -5
- package/src/ai/chat-task-contracts.ts +4 -1
- package/src/ai/chat-task-reasons-migrate.ts +7 -0
- package/src/ai/chat-task-reasons.ts +1 -1
- package/src/ai/chat-tasks.ts +55 -25
- package/src/ai/client/controller.ts +59 -0
- package/src/ai/default-tools.ts +9 -1
- package/src/ai/executor.ts +198 -39
- package/src/ai/firecrawl-tools.ts +15 -2
- package/src/ai/index.ts +3 -0
- package/src/ai/migrate.ts +26 -2
- package/src/ai/open-questions.ts +172 -0
- package/src/ai/projects-routes.ts +3 -0
- package/src/ai/protocol.ts +14 -2
- package/src/ai/question-text.ts +8 -0
- package/src/ai/recent-resources.ts +240 -0
- package/src/ai/recent-sent-refs.ts +38 -0
- package/src/ai/routes.ts +195 -5
- package/src/ai/runtime.ts +1 -0
- package/src/ai/skills-routes.ts +3 -0
- package/src/ai/skills.ts +10 -1
- package/src/ai/store.ts +245 -24
- package/src/ai/testing.ts +36 -0
- package/src/ai/tool-audit.ts +8 -0
- package/src/ai/tool-sentences.ts +152 -34
- package/src/ai/tool-wording.ts +36 -8
- package/src/ai/tools.ts +8 -3
- package/src/ai/types.ts +43 -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-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 +2 -0
- package/src/api/app-approval-actions.ts +31 -7
- package/src/api/app-approval.ts +46 -5
- package/src/api/approvals-loader.ts +23 -1
- package/src/api/approvals.ts +2 -0
- package/src/api/auth/schemas.ts +3 -0
- package/src/api/auth.ts +67 -12
- 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 +6 -1
- package/src/api/lasting-access-messages.ts +17 -0
- package/src/api/mcp.ts +30 -3
- package/src/api/me-panel-consent.ts +71 -0
- package/src/api/me.ts +159 -36
- 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 +166 -262
- package/src/approvals/approval-card.ts +68 -0
- package/src/approvals/messages.ts +12 -24
- package/src/browser/CloudResourceSearch.browser-harness.tsx +10 -2
- package/src/browser/app-approval.ts +54 -5
- package/src/browser/commands.ts +22 -6
- package/src/browser/composer-catalog.ts +45 -3
- package/src/browser/composer-insert.ts +10 -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 +11 -0
- package/src/browser/panel-shortcuts.ts +31 -0
- package/src/browser/panel-slot.ts +412 -10
- package/src/browser/panel.ts +37 -0
- 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 +123 -16
- 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/seen-hints.ts +35 -0
- package/src/contracts/shared.ts +3 -0
- package/src/server/client-ip.ts +5 -0
- package/src/server/help-payload.ts +38 -0
- package/src/server/middleware/auth.ts +13 -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/services/accounts/app.ts +23 -3
- package/src/services/accounts/users.ts +12 -1
- package/src/services/app-approval.ts +382 -59
- package/src/services/approvals/device.ts +49 -31
- package/src/services/approvals/index.ts +7 -5
- package/src/services/approvals/list.ts +27 -6
- package/src/services/approvals/question.ts +68 -0
- package/src/services/audit/index.ts +1 -0
- package/src/services/auth-flows/ipa.ts +39 -2
- package/src/services/auth-flows/notification-sender.ts +3 -0
- package/src/services/auth-flows/password-reset.ts +32 -6
- 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 +13 -0
- 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/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/markdown.ts +160 -2
- package/src/services/service-account-credentials.ts +21 -0
- package/src/services/session/index.ts +296 -41
- package/src/services/session/recent.ts +13 -0
- package/src/services/webauthn.ts +12 -0
- package/src/shared/email-html.ts +2 -0
- package/src/shared/help.ts +3 -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 +10 -2
- package/src/ssr/HelpPanelArticle.island.tsx +308 -0
- package/src/ssr/HelpPanelBack.island.tsx +32 -0
- package/src/ssr/HelpPanelMenu.island.tsx +133 -0
- package/src/ssr/HelpPanelSearch.island.tsx +123 -0
- package/src/ssr/HelpPanelShortcuts.island.tsx +22 -0
- package/src/ssr/HelpPanelView.island.tsx +27 -0
- package/src/ssr/Layout.tsx +25 -2
- package/src/ssr/LayoutHelp.browser-harness.tsx +2 -0
- package/src/ssr/LayoutHelp.tsx +222 -419
- package/src/ssr/LayoutHelpPanel.browser-harness.tsx +135 -0
- package/src/ssr/LayoutHelpPanel.server.browser-harness.tsx +131 -0
- package/src/ssr/LayoutHelpPanel.server.tsx +115 -0
- package/src/ssr/LayoutHelpTrigger.tsx +27 -2
- package/src/ssr/MobileNavigation.tsx +2 -2
- package/src/ssr/PanelLayout.tsx +9 -1
- package/src/ssr/PanelRuntime.island.tsx +7 -0
- package/src/ssr/PanelSlot.tsx +53 -3
- package/src/ssr/ShellRuntime.island.tsx +11 -2
- package/src/ssr/__snapshots__/LayoutHelp.ssr.render.test.tsx.snap +29 -0
- package/src/ssr/app-color.ts +14 -9
- package/src/ssr/app-navigation.ts +16 -3
- package/src/ssr/compact-shell.browser-harness.ts +30 -22
- package/src/ssr/dialog-app-colors.browser-harness.tsx +43 -0
- package/src/ssr/help-messages.ts +4 -0
- package/src/ssr/help.ts +21 -0
- package/src/ssr/hints.ts +25 -0
- package/src/ssr/index.ts +3 -0
- package/src/ssr/layout-context.ts +2 -0
- package/src/ssr/layout-help-article.server.ts +50 -0
- package/src/ssr/layout-help-panel-controls.tsx +43 -0
- package/src/ssr/layout-help-panel-document.ts +67 -0
- package/src/ssr/layout-help-panel-lifecycle.ts +32 -0
- package/src/ssr/layout-help-panel-menu.tsx +113 -0
- package/src/ssr/layout-help-panel-modules.ts +7 -0
- package/src/ssr/layout-help-panel-reader.tsx +159 -0
- package/src/ssr/layout-help-panel-search.tsx +69 -0
- package/src/ssr/layout-help-panel-shortcuts.ts +7 -0
- package/src/ssr/layout-help-panel-state.ts +141 -0
- package/src/ssr/layout-help-panel-types.ts +19 -0
- package/src/ssr/layout-help-start.ts +33 -7
- package/src/ssr/layout-help-views.tsx +484 -0
- package/src/ssr/panel-consent.ts +29 -0
- package/src/ssr/panel-context.ts +10 -0
- package/src/ssr/panel-idle.ts +9 -0
- package/src/ssr/panel-slot.browser-harness.tsx +53 -4
- package/src/ssr/platform-messages.ts +18 -0
- package/src/ssr/shell-data.tsx +2 -1
- package/src/styles/approvals.css +6 -0
- package/src/styles/effects.css +213 -9
- package/src/styles/file-chooser.css +2 -1
- package/src/styles/global.css +49 -12
- package/src/styles/panel.css +47 -4
- package/src/styles/resource-search.css +6 -2
- package/src/styles/tokens.css +17 -11
- 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 +17 -22
|
@@ -0,0 +1,288 @@
|
|
|
1
|
+
import { dates, i18n } from "@k2b/stdlib";
|
|
2
|
+
import { capabilityPersonText, capabilitySentenceText } from "../../_internal/capability-sentences";
|
|
3
|
+
import type { CapabilityAskReason, CapabilityPerson, CapabilitySentence } from "../../contracts/capabilities";
|
|
4
|
+
import { formatBytes } from "../../shared/format";
|
|
5
|
+
import type { ActionCard } from "./action-card";
|
|
6
|
+
import { actionMessages } from "./action-messages";
|
|
7
|
+
|
|
8
|
+
export type ActionTextRow = { label: string; value: string; format?: "date" | "date-time"; block?: true };
|
|
9
|
+
export type ActionQuestionText = {
|
|
10
|
+
locale: string;
|
|
11
|
+
question: string;
|
|
12
|
+
reason: string;
|
|
13
|
+
reasonCode: CapabilityAskReason;
|
|
14
|
+
rows: ActionTextRow[];
|
|
15
|
+
readMore?: string;
|
|
16
|
+
confirm: string;
|
|
17
|
+
decline: string;
|
|
18
|
+
incomplete: boolean;
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
const messages = i18n.define({
|
|
22
|
+
baseLocale: "en",
|
|
23
|
+
messages: {
|
|
24
|
+
en: {
|
|
25
|
+
from: "From",
|
|
26
|
+
to: "To",
|
|
27
|
+
cc: "Cc",
|
|
28
|
+
bcc: "Bcc",
|
|
29
|
+
subject: "Subject",
|
|
30
|
+
sendAt: "Send at",
|
|
31
|
+
warning: "Warning",
|
|
32
|
+
attachments: "Attachments",
|
|
33
|
+
text: "Text",
|
|
34
|
+
when: "When",
|
|
35
|
+
until: "Until",
|
|
36
|
+
where: "Where",
|
|
37
|
+
with: "With",
|
|
38
|
+
repeats: "Repeats",
|
|
39
|
+
due: "Due",
|
|
40
|
+
assignees: "Assignees",
|
|
41
|
+
status: "Status",
|
|
42
|
+
checklist: "Checklist",
|
|
43
|
+
tags: "Tags",
|
|
44
|
+
folder: "Folder",
|
|
45
|
+
size: "Size",
|
|
46
|
+
target: "To",
|
|
47
|
+
place: "Place",
|
|
48
|
+
added: "Added",
|
|
49
|
+
removed: "Removed",
|
|
50
|
+
starts: "Starts",
|
|
51
|
+
may: "May",
|
|
52
|
+
mayAdded: "May (added)",
|
|
53
|
+
mayRemoved: "May (removed)",
|
|
54
|
+
also: "Also",
|
|
55
|
+
readAll: "Read all",
|
|
56
|
+
title: "Title",
|
|
57
|
+
name: "Name",
|
|
58
|
+
note: "Note",
|
|
59
|
+
before: ({ label }: { label: string }) => `${label} (before)`,
|
|
60
|
+
more: ({ count }: { count: number }) => `${count.toLocaleString("en")} more`,
|
|
61
|
+
},
|
|
62
|
+
de: {
|
|
63
|
+
from: "Von",
|
|
64
|
+
to: "An",
|
|
65
|
+
cc: "Cc",
|
|
66
|
+
bcc: "Bcc",
|
|
67
|
+
subject: "Betreff",
|
|
68
|
+
sendAt: "Senden am",
|
|
69
|
+
warning: "Warnung",
|
|
70
|
+
attachments: "Anhänge",
|
|
71
|
+
text: "Text",
|
|
72
|
+
when: "Wann",
|
|
73
|
+
until: "Bis",
|
|
74
|
+
where: "Wo",
|
|
75
|
+
with: "Mit",
|
|
76
|
+
repeats: "Wiederholung",
|
|
77
|
+
due: "Fällig",
|
|
78
|
+
assignees: "Zuständig",
|
|
79
|
+
status: "Status",
|
|
80
|
+
checklist: "Checkliste",
|
|
81
|
+
tags: "Tags",
|
|
82
|
+
folder: "Ordner",
|
|
83
|
+
size: "Größe",
|
|
84
|
+
target: "Ziel",
|
|
85
|
+
place: "Ort",
|
|
86
|
+
added: "Neu",
|
|
87
|
+
removed: "Entfernt",
|
|
88
|
+
starts: "Startet",
|
|
89
|
+
may: "Darf",
|
|
90
|
+
mayAdded: "Darf (neu)",
|
|
91
|
+
mayRemoved: "Darf (entfernt)",
|
|
92
|
+
also: "Folge",
|
|
93
|
+
readAll: "Ganz lesen",
|
|
94
|
+
title: "Titel",
|
|
95
|
+
name: "Name",
|
|
96
|
+
note: "Hinweis",
|
|
97
|
+
before: ({ label }) => `${label} (vorher)`,
|
|
98
|
+
more: ({ count }) => `${count.toLocaleString("de")} weitere`,
|
|
99
|
+
},
|
|
100
|
+
},
|
|
101
|
+
});
|
|
102
|
+
export const checkActionTextMessages = () => messages.check();
|
|
103
|
+
/** The label for a row an old review left without one, where a transport requires labels. */
|
|
104
|
+
export const actionNoteLabel = (locale: string): string => messages.resolve([locale]).t.note;
|
|
105
|
+
|
|
106
|
+
/** Flatten the question card once. Transport limits and terminal escaping belong to its callers. */
|
|
107
|
+
export const actionCardText = (card: ActionCard): ActionQuestionText => {
|
|
108
|
+
const t = messages.resolve([card.locale]).t;
|
|
109
|
+
const rows: ActionTextRow[] = [];
|
|
110
|
+
let incomplete = false;
|
|
111
|
+
const row = (label: string, value: string | undefined, extra: Pick<ActionTextRow, "block" | "format"> = {}) => {
|
|
112
|
+
if (value !== undefined && value !== "") rows.push({ label, value, ...extra });
|
|
113
|
+
};
|
|
114
|
+
const list = (values: string[], more = 0) => {
|
|
115
|
+
if (more > 0) {
|
|
116
|
+
incomplete = true;
|
|
117
|
+
values.push(t.more({ count: more }));
|
|
118
|
+
}
|
|
119
|
+
return new Intl.ListFormat(card.locale, { type: "conjunction" }).format(values);
|
|
120
|
+
};
|
|
121
|
+
const people = (values: CapabilityPerson[] | undefined, more = 0) => list((values ?? []).map(capabilityPersonText), more);
|
|
122
|
+
// The thing's own name, unless the question already says it.
|
|
123
|
+
const named = (label: string, value: string) => {
|
|
124
|
+
if (!card.questionText.includes(value)) row(label, value);
|
|
125
|
+
};
|
|
126
|
+
const model = card.preview;
|
|
127
|
+
if (model?.kind === "legacy") {
|
|
128
|
+
rows.push(...model.rows);
|
|
129
|
+
row(t.text, model.markdown, { block: true });
|
|
130
|
+
} else if (model?.kind === "review") {
|
|
131
|
+
const context = { locale: card.locale, timeZone: model.timeZone };
|
|
132
|
+
const sentence = (value: CapabilitySentence) => capabilitySentenceText(value.text, value.slots ?? model.slots ?? {}, context);
|
|
133
|
+
const preview = model.preview;
|
|
134
|
+
if (preview && "truncated" in preview && preview.truncated) incomplete = true;
|
|
135
|
+
switch (preview?.kind) {
|
|
136
|
+
case "message":
|
|
137
|
+
row(t.from, preview.from && capabilityPersonText(preview.from));
|
|
138
|
+
row(t.to, people(preview.to, preview.more?.to));
|
|
139
|
+
row(t.cc, people(preview.cc, preview.more?.cc));
|
|
140
|
+
row(t.bcc, people(preview.bcc, preview.more?.bcc));
|
|
141
|
+
row(t.subject, preview.subject);
|
|
142
|
+
row(t.sendAt, preview.sendAt, { format: "date-time" });
|
|
143
|
+
row(t.warning, preview.warnings?.join(" · "));
|
|
144
|
+
row(
|
|
145
|
+
t.attachments,
|
|
146
|
+
list(
|
|
147
|
+
(preview.attachments ?? []).map((file) =>
|
|
148
|
+
file.size === undefined ? file.name : `${file.name} (${formatBytes(file.size, { locale: card.locale })})`,
|
|
149
|
+
),
|
|
150
|
+
preview.moreAttachments,
|
|
151
|
+
),
|
|
152
|
+
);
|
|
153
|
+
row(t.text, preview.body, { block: true });
|
|
154
|
+
break;
|
|
155
|
+
case "event": {
|
|
156
|
+
named(t.title, preview.title);
|
|
157
|
+
const dateOnly = (value: string) => /^\d{4}-\d{2}-\d{2}$/.test(value);
|
|
158
|
+
const allDay = preview.allDay === true || dateOnly(preview.start);
|
|
159
|
+
// Both ends stay typed, so a device shows them in its own zone. An all-day instant becomes its calendar day
|
|
160
|
+
// in the card's zone first, so no reader's zone moves it to another day.
|
|
161
|
+
const day = (value: string) =>
|
|
162
|
+
dateOnly(value)
|
|
163
|
+
? value
|
|
164
|
+
: new Intl.DateTimeFormat("en-CA", { timeZone: model.timeZone, year: "numeric", month: "2-digit", day: "2-digit" }).format(
|
|
165
|
+
new Date(value),
|
|
166
|
+
);
|
|
167
|
+
const format = allDay ? "date" : "date-time";
|
|
168
|
+
row(t.when, allDay ? day(preview.start) : preview.start, { format });
|
|
169
|
+
if (preview.end) row(t.until, allDay ? day(preview.end) : preview.end, { format });
|
|
170
|
+
row(t.where, [preview.place, preview.location].filter(Boolean).join(" · "));
|
|
171
|
+
row(t.with, people(preview.attendees));
|
|
172
|
+
row(t.repeats, preview.recurrence);
|
|
173
|
+
row(t.text, preview.description, { block: true });
|
|
174
|
+
break;
|
|
175
|
+
}
|
|
176
|
+
case "task":
|
|
177
|
+
named(t.title, preview.title);
|
|
178
|
+
row(t.place, preview.place);
|
|
179
|
+
row(t.due, preview.due, { format: /^\d{4}-\d{2}-\d{2}$/.test(preview.due ?? "") ? "date" : "date-time" });
|
|
180
|
+
row(t.assignees, people(preview.assignees));
|
|
181
|
+
row(t.status, preview.status);
|
|
182
|
+
row(t.checklist, preview.checklist?.map((item) => `${item.done ? "☑" : "☐"} ${item.label}`).join(" · "));
|
|
183
|
+
row(t.tags, preview.tags?.join(", "));
|
|
184
|
+
row(t.text, preview.description, { block: true });
|
|
185
|
+
break;
|
|
186
|
+
case "file":
|
|
187
|
+
named(t.name, preview.name);
|
|
188
|
+
row(t.folder, preview.place ?? preview.path);
|
|
189
|
+
row(t.size, preview.size === undefined ? undefined : formatBytes(preview.size, { locale: card.locale }));
|
|
190
|
+
row(t.target, preview.to);
|
|
191
|
+
break;
|
|
192
|
+
case "page":
|
|
193
|
+
named(t.title, preview.title);
|
|
194
|
+
row(t.place, preview.place);
|
|
195
|
+
row(t.text, preview.body, { block: true });
|
|
196
|
+
for (const change of preview.changes ?? []) row(change.change === "added" ? t.added : t.removed, change.text);
|
|
197
|
+
break;
|
|
198
|
+
case "rule":
|
|
199
|
+
row(t.starts, sentence(preview.when));
|
|
200
|
+
row(t.text, preview.does, { block: true });
|
|
201
|
+
for (const may of preview.may)
|
|
202
|
+
row(may.mark === "added" ? t.mayAdded : may.mark === "removed" ? t.mayRemoved : t.may, sentence(may));
|
|
203
|
+
// The card's fixed line: a routine knows only its instruction, never this chat.
|
|
204
|
+
if (!preview.seesChat) row(t.note, actionMessages(card.locale).ruleBlindToChat);
|
|
205
|
+
break;
|
|
206
|
+
case "fields":
|
|
207
|
+
for (const field of preview.rows) {
|
|
208
|
+
rows.push({ label: field.label, value: field.value, ...(field.format ? { format: field.format } : {}) });
|
|
209
|
+
if (field.before !== undefined)
|
|
210
|
+
rows.push({ label: t.before({ label: field.label }), value: field.before, ...(field.format ? { format: field.format } : {}) });
|
|
211
|
+
}
|
|
212
|
+
break;
|
|
213
|
+
}
|
|
214
|
+
for (const consequence of model.consequences)
|
|
215
|
+
row(consequence.mark === "added" ? t.added : consequence.mark === "removed" ? t.removed : t.also, consequence.plain);
|
|
216
|
+
}
|
|
217
|
+
// A long question shortens slot values and names at most three people of a slot, then "N more". The text is
|
|
218
|
+
// complete only when the question or a row shows each value whole; people the app left out are never shown.
|
|
219
|
+
const shown = [card.questionText, ...rows.map((entry) => entry.value)];
|
|
220
|
+
const whole = (value: string) => shown.some((text) => text.includes(value));
|
|
221
|
+
const typed = (value: string, format: "date" | "date-time") =>
|
|
222
|
+
rows.some((entry) => entry.value === value && (entry.format === "date-time" || entry.format === format));
|
|
223
|
+
if (
|
|
224
|
+
card.slotValues?.unlisted ||
|
|
225
|
+
card.slotValues?.values.some((value) => !whole(value)) ||
|
|
226
|
+
card.slotValues?.times.some(
|
|
227
|
+
(time) => !whole(time.text) && !(typed(time.start, time.format) && (!time.end || typed(time.end, time.format))),
|
|
228
|
+
)
|
|
229
|
+
)
|
|
230
|
+
incomplete = true;
|
|
231
|
+
return {
|
|
232
|
+
locale: card.locale,
|
|
233
|
+
question: card.questionText,
|
|
234
|
+
reason: card.reason.text,
|
|
235
|
+
reasonCode: card.reason.code,
|
|
236
|
+
rows,
|
|
237
|
+
...(card.readMore ? { readMore: card.readMore } : {}),
|
|
238
|
+
confirm: card.confirm.label,
|
|
239
|
+
decline: card.decline,
|
|
240
|
+
incomplete,
|
|
241
|
+
};
|
|
242
|
+
};
|
|
243
|
+
|
|
244
|
+
/** The CLI's plain question block, before terminal-safe sanitizing at the CLI boundary. */
|
|
245
|
+
export const actionQuestionLines = (
|
|
246
|
+
text: ActionQuestionText,
|
|
247
|
+
extra: { origin?: string; commands?: { approve: string; reject: string }; timeZone: string },
|
|
248
|
+
): string[] => {
|
|
249
|
+
const t = messages.resolve([text.locale]).t;
|
|
250
|
+
const lines = [`? ${text.question}`, ` ${text.reason}`];
|
|
251
|
+
if (extra.origin) lines.push(` ${extra.origin}`);
|
|
252
|
+
const width = Math.max(0, ...text.rows.filter((row) => !row.block && row.label).map((row) => Array.from(row.label).length + 1));
|
|
253
|
+
let markedIncomplete = false;
|
|
254
|
+
for (const row of text.rows) {
|
|
255
|
+
if (row.block) {
|
|
256
|
+
const body = row.value.split(/\r?\n/);
|
|
257
|
+
// Cut a long text only when "Read all" can open the whole thing; otherwise the terminal shows all of it.
|
|
258
|
+
const shown = text.readMore ? body.slice(0, 8) : body;
|
|
259
|
+
lines.push(...shown.map((line) => ` ${line}`));
|
|
260
|
+
if (shown.length < body.length || (text.incomplete && !markedIncomplete)) {
|
|
261
|
+
lines.push(" …");
|
|
262
|
+
markedIncomplete = true;
|
|
263
|
+
}
|
|
264
|
+
continue;
|
|
265
|
+
}
|
|
266
|
+
const options = { locale: text.locale, timeZone: extra.timeZone };
|
|
267
|
+
const value =
|
|
268
|
+
row.format === "date-time"
|
|
269
|
+
? dates.formatDateTime(row.value, options)
|
|
270
|
+
: row.format === "date"
|
|
271
|
+
? dates.formatDate(/^\d{4}-\d{2}-\d{2}$/.test(row.value) ? `${row.value}T12:00:00.000Z` : row.value, {
|
|
272
|
+
...options,
|
|
273
|
+
...(/^\d{4}-\d{2}-\d{2}$/.test(row.value) ? { timeZone: "UTC" } : {}),
|
|
274
|
+
})
|
|
275
|
+
: row.value;
|
|
276
|
+
lines.push(row.label ? ` ${(row.label + ":").padEnd(width)} ${value}` : ` ${value}`);
|
|
277
|
+
}
|
|
278
|
+
if (text.incomplete && !markedIncomplete) lines.push(" …");
|
|
279
|
+
if (text.readMore) lines.push(` ${t.readAll}: ${text.readMore}`);
|
|
280
|
+
if (extra.commands) {
|
|
281
|
+
const width = Math.max(Array.from(text.confirm).length, Array.from(text.decline).length) + 1;
|
|
282
|
+
lines.push(
|
|
283
|
+
` ${(text.confirm + ":").padEnd(width)} ${extra.commands.approve}`,
|
|
284
|
+
` ${(text.decline + ":").padEnd(width)} ${extra.commands.reject}`,
|
|
285
|
+
);
|
|
286
|
+
}
|
|
287
|
+
return lines;
|
|
288
|
+
};
|
package/src/ai/chat/blocks.tsx
CHANGED
|
@@ -3,6 +3,7 @@ import { mutation } from "@k2b/stdlib/solid";
|
|
|
3
3
|
import { Button, ButtonLink, Chat, isStructuredDataValue, MarkdownView, SplitButton, StructuredDataPreview, useLocale } from "@k2b/ui";
|
|
4
4
|
import { createSignal, createUniqueId, For, type JSX, Match, Show, Switch, untrack } from "solid-js";
|
|
5
5
|
import { capabilityApprovalReason, capabilityApprovalReasonLabel } from "../../_internal/capability-sentences";
|
|
6
|
+
import { capabilityReviewView } from "../../capabilities/reviews";
|
|
6
7
|
import type { CapabilityActionReview } from "../../contracts/capabilities";
|
|
7
8
|
import { markdown } from "../../shared";
|
|
8
9
|
import type { AiTurnBlock } from "../protocol";
|
|
@@ -31,8 +32,10 @@ import {
|
|
|
31
32
|
} from "./message-utils";
|
|
32
33
|
import { aiChatMessages } from "./messages";
|
|
33
34
|
import { AssistantMarkdownBlock } from "./primitives";
|
|
35
|
+
import { ToolActionView } from "./tool-action";
|
|
34
36
|
import { AiToolActivity } from "./tool-disclosure";
|
|
35
37
|
import { isFailedTool } from "./tool-groups";
|
|
38
|
+
import type { AiTurnActionState } from "./turn-layout";
|
|
36
39
|
import { CloudCardBlock, CloudSurveyBlock, CloudSurveyResultBlock, CloudTextEditorBlock, CloudTextEditorResultBlock } from "./visual-tools";
|
|
37
40
|
import { FetchFileToolBlock, WebExtractToolBlock, WebSearchToolBlock, WebsiteReceipts, websiteReceipts } from "./web-tools";
|
|
38
41
|
|
|
@@ -199,6 +202,7 @@ export function ApprovalBlockView(props: { turnId: string; block: ToolBlock; onD
|
|
|
199
202
|
const locale = useLocale();
|
|
200
203
|
const timeZone = useReaderTimeZone();
|
|
201
204
|
const t = () => aiChatMessages(locale());
|
|
205
|
+
const review = () => (props.block.approval?.review ? capabilityReviewView(props.block.approval.review, { timeZone }) : undefined);
|
|
202
206
|
const request = () => ({ turnId: props.turnId, callId: props.block.callId, name: props.block.name });
|
|
203
207
|
const pending = () => props.block.status === "awaiting_approval";
|
|
204
208
|
const actionDisabled = () => actions.actionDisabled?.() ?? false;
|
|
@@ -229,7 +233,7 @@ export function ApprovalBlockView(props: { turnId: string; block: ToolBlock; onD
|
|
|
229
233
|
// The model's own reason, labelled as such, only where Cloud offered it; it adds to the app's sentence and never replaces it.
|
|
230
234
|
const reason = () => (props.block.presentation?.approvalReason ? capabilityApprovalReason(props.block.args) : null);
|
|
231
235
|
const description = () => {
|
|
232
|
-
const reviewMessage =
|
|
236
|
+
const reviewMessage = review()?.message.trim();
|
|
233
237
|
if (reviewMessage) return reviewMessage;
|
|
234
238
|
// An app Action's approval text repeats its heading and reason for text-only readers; the card shows them itself.
|
|
235
239
|
if (capability()) return null;
|
|
@@ -252,10 +256,9 @@ export function ApprovalBlockView(props: { turnId: string; block: ToolBlock; onD
|
|
|
252
256
|
const match = /^([^:]{1,40}):\s+(.+)$/.exec(line);
|
|
253
257
|
return match ? { label: match[1], value: match[2] } : { value: line };
|
|
254
258
|
});
|
|
255
|
-
const inlineReviewDetails = () =>
|
|
256
|
-
|
|
257
|
-
const
|
|
258
|
-
const reviewLinks = () => props.block.approval?.review?.links ?? [];
|
|
259
|
+
const inlineReviewDetails = () => (review()?.details ?? []).filter((detail) => (detail.display ?? "inline") === "inline");
|
|
260
|
+
const blockReviewDetails = () => (review()?.details ?? []).filter((detail) => detail.display === "block");
|
|
261
|
+
const reviewLinks = () => review()?.links ?? [];
|
|
259
262
|
const reviewLinkTitle = (rel: "open" | "edit" | "status" | "preview" | "download") =>
|
|
260
263
|
({
|
|
261
264
|
open: t().openIn({ app: ownerName() }),
|
|
@@ -545,20 +548,6 @@ function CapabilityToolView(props: { block: ToolBlock }) {
|
|
|
545
548
|
);
|
|
546
549
|
}
|
|
547
550
|
|
|
548
|
-
function RejectedToolView(props: { block: ToolBlock }) {
|
|
549
|
-
const locale = useLocale();
|
|
550
|
-
const timeZone = useReaderTimeZone();
|
|
551
|
-
const presentation = () => props.block.presentation;
|
|
552
|
-
const title = () => toolSubject(props.block, locale(), timeZone) ?? presentation()?.title ?? displayToolName(props.block.name, locale());
|
|
553
|
-
return (
|
|
554
|
-
<Chat.Activity
|
|
555
|
-
icon={aiToolIcon(props.block.name, presentation()?.appIcon)}
|
|
556
|
-
label={toolOutcome(props.block, "rejected", locale(), timeZone) ?? aiChatMessages(locale()).toolRejected({ title: title() })}
|
|
557
|
-
accent={presentation()?.appAccent}
|
|
558
|
-
/>
|
|
559
|
-
);
|
|
560
|
-
}
|
|
561
|
-
|
|
562
551
|
export function SurveyToolView(props: { turnId: string; block: ToolBlock; active?: boolean }) {
|
|
563
552
|
const actions = useAiChatActions();
|
|
564
553
|
const locale = useLocale();
|
|
@@ -670,6 +659,15 @@ function ToolBlockView(props: { turnId: string; block: ToolBlock; active?: boole
|
|
|
670
659
|
const actions = useAiChatActions();
|
|
671
660
|
const locale = useLocale();
|
|
672
661
|
const status = () => props.block.status;
|
|
662
|
+
// The same states as place 4 of a turn: open, then running, done, rejected, failed, or not run.
|
|
663
|
+
const actionState = (): AiTurnActionState | "open" | undefined => {
|
|
664
|
+
if (status() === "awaiting_approval") return "open";
|
|
665
|
+
if (status() === "rejected") return "rejected";
|
|
666
|
+
const action = props.block.presentation?.kind === "capability" && props.block.presentation.capabilityKind === "action";
|
|
667
|
+
if (!action) return undefined;
|
|
668
|
+
if (status() === "running" || status() === "awaiting_client") return props.active === false ? "not_run" : "running";
|
|
669
|
+
return isFailedTool(props.block) ? "failed" : "done";
|
|
670
|
+
};
|
|
673
671
|
const fetchError = () =>
|
|
674
672
|
props.block.name === "fetch_file" && props.block.isError ? fetchFileErrorPresentation(props.block.result, locale()) : undefined;
|
|
675
673
|
return (
|
|
@@ -688,12 +686,8 @@ function ToolBlockView(props: { turnId: string; block: ToolBlock; active?: boole
|
|
|
688
686
|
/>
|
|
689
687
|
}
|
|
690
688
|
>
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
</Match>
|
|
694
|
-
<Match when={status() === "rejected"}>
|
|
695
|
-
<RejectedToolView block={props.block} />
|
|
696
|
-
</Match>
|
|
689
|
+
{/* A question, and every outcome of an app Action, is one card v4 element. */}
|
|
690
|
+
<Match when={actionState()}>{(state) => <ToolActionView block={props.block} turnId={props.turnId} state={state()} />}</Match>
|
|
697
691
|
<Match when={props.block.presentation?.kind === "capability"}>
|
|
698
692
|
<CapabilityToolView block={props.block} />
|
|
699
693
|
</Match>
|
|
@@ -12,6 +12,7 @@ import {
|
|
|
12
12
|
} from "@k2b/ui";
|
|
13
13
|
import { createContext, createSignal, For, type JSX, Show, useContext } from "solid-js";
|
|
14
14
|
import type { CloudResourceRef } from "../../contracts/capabilities";
|
|
15
|
+
import type { AiActionCheck } from "../action-review";
|
|
15
16
|
import type { AiTurnBlock } from "../protocol";
|
|
16
17
|
import type { AiMessageFeedback, AiMessageFeedbackReason, AiStoredMessage } from "../types";
|
|
17
18
|
import {
|
|
@@ -46,6 +47,11 @@ export type AiChatActions = {
|
|
|
46
47
|
/** Revoke the approval that lets this chat read `origin` without asking; the next request asks again. */
|
|
47
48
|
onRevokeWebsite?: (origin: string) => Promise<void>;
|
|
48
49
|
onFrontendToolResult?: FrontendToolResultHandler;
|
|
50
|
+
/**
|
|
51
|
+
* Ask the owning app once more whether a call whose outcome stayed unknown happened, through its `check` Query. Never
|
|
52
|
+
* repeats the call itself. Resolves with the stored check; without it, an unknown outcome offers no new check.
|
|
53
|
+
*/
|
|
54
|
+
onCheckAction?: (request: AiTurnActionRequest) => Promise<AiActionCheck>;
|
|
49
55
|
onForkMessage?: ForkMessageHandler;
|
|
50
56
|
onRetryMessage?: RetryMessageHandler;
|
|
51
57
|
onRetrySteer?: RetrySteerHandler;
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { capabilityUnknownText } from "../../_internal/capability-sentences";
|
|
2
|
+
import type { AiTurnBlock } from "../protocol";
|
|
3
|
+
import { toolOutcome } from "../tool-wording";
|
|
4
|
+
import { isRecord } from "./message-utils";
|
|
5
|
+
import { aiChatMessages } from "./messages";
|
|
6
|
+
|
|
7
|
+
type ToolBlock = Extract<AiTurnBlock, { kind: "tool" }>;
|
|
8
|
+
|
|
9
|
+
/** The application's own short summary of a result, if it fits on one line. */
|
|
10
|
+
const resultSummary = (block: ToolBlock): string | undefined => {
|
|
11
|
+
const value = isRecord(block.result) ? block.result.summary : undefined;
|
|
12
|
+
return typeof value === "string" && value.trim() && value.trim().length <= 500 ? value.trim() : undefined;
|
|
13
|
+
};
|
|
14
|
+
|
|
15
|
+
/** The words of a call without an app sentence: Cloud's frames around its catalog sentence or title. */
|
|
16
|
+
export const legacyOutcomeWords = (block: ToolBlock, locale: string, timeZone: string) => (subject: string) => {
|
|
17
|
+
const t = aiChatMessages(locale);
|
|
18
|
+
const capability = block.presentation?.kind === "capability";
|
|
19
|
+
return {
|
|
20
|
+
running: t.receiptRunning({ title: subject }),
|
|
21
|
+
// The app's done sentence, else its own summary of the call, else Cloud's frame.
|
|
22
|
+
done:
|
|
23
|
+
toolOutcome(block, "done", locale, timeZone) ??
|
|
24
|
+
resultSummary(block) ??
|
|
25
|
+
(capability ? t.receiptDone({ title: subject }) : t.receiptApproved({ title: subject })),
|
|
26
|
+
rejected: toolOutcome(block, "rejected", locale, timeZone) ?? t.receiptRejected({ title: subject }),
|
|
27
|
+
notRun: toolOutcome(block, "notRun", locale, timeZone) ?? t.receiptNotRun({ title: subject, stopped: false }),
|
|
28
|
+
unknown: capabilityUnknownText(subject, locale),
|
|
29
|
+
unknownStill: capabilityUnknownText(subject, locale, true),
|
|
30
|
+
};
|
|
31
|
+
};
|