@k2b/cloud 0.40.0 → 0.42.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 +9 -5
- package/src/_internal/bounded-json.ts +14 -2
- package/src/_internal/capabilities.ts +87 -8
- package/src/_internal/capability-reviews.ts +6 -0
- package/src/_internal/capability-sentences.ts +2 -1
- package/src/_internal/capability-timeout.ts +71 -0
- package/src/_internal/define-app.ts +158 -92
- package/src/_internal/heartbeat.ts +203 -14
- package/src/_internal/http-drain.ts +56 -0
- package/src/_internal/registry-validation.ts +3 -0
- package/src/_internal/registry.ts +28 -1
- package/src/_internal/runtime-context.ts +1 -0
- package/src/_internal/setup-lock.ts +103 -0
- package/src/_internal/status-preserving-ssr.ts +2 -0
- package/src/access/PermissionEditor.tsx +16 -17
- package/src/access/PrincipalPicker.tsx +45 -10
- package/src/access/messages.ts +8 -0
- package/src/access/principal-label.ts +18 -0
- package/src/access/ui.ts +1 -0
- package/src/account/EntitySearch.tsx +5 -4
- package/src/ai/action-review.ts +2 -1
- package/src/ai/app-skill-store.ts +1 -1
- package/src/ai/capabilities.ts +21 -3
- package/src/ai/capability-execution.ts +14 -12
- package/src/ai/chat/action-card.ts +76 -4
- package/src/ai/chat/action-messages.ts +14 -0
- package/src/ai/chat/action-question.browser-harness.tsx +12 -0
- package/src/ai/chat/action-question.tsx +214 -23
- package/src/ai/chat/action-text.ts +288 -0
- package/src/ai/chat/tool-action-words.ts +31 -0
- package/src/ai/chat/tool-action.tsx +1 -26
- package/src/ai/chat-tasks.ts +2 -1
- package/src/ai/client/controller.ts +150 -32
- package/src/ai/client/transport.ts +14 -2
- package/src/ai/code-capability-routes.ts +27 -23
- package/src/ai/code-runtime-tools.ts +14 -16
- package/src/ai/code-source-tools.ts +16 -18
- package/src/ai/executor.ts +64 -21
- package/src/ai/fetch-file-tool.ts +67 -76
- package/src/ai/firecrawl-tools.ts +15 -2
- package/src/ai/http.ts +1 -0
- package/src/ai/index.ts +1 -0
- package/src/ai/migrate.ts +28 -0
- package/src/ai/open-questions.ts +168 -0
- package/src/ai/projects.ts +1 -2
- package/src/ai/protocol.ts +2 -2
- package/src/ai/question-text.ts +8 -0
- package/src/ai/recent-resources.ts +12 -5
- package/src/ai/review-content.ts +77 -0
- package/src/ai/routes.ts +119 -35
- package/src/ai/runtime.ts +1 -0
- package/src/ai/skill-tool.ts +17 -6
- package/src/ai/skills-routes.ts +72 -27
- package/src/ai/skills.ts +139 -60
- package/src/ai/store.ts +184 -31
- package/src/ai/testing.ts +36 -0
- package/src/ai/tool-sentences.ts +152 -34
- package/src/ai/tool-wording.ts +12 -6
- package/src/ai/tools.ts +8 -3
- package/src/ai/types.ts +25 -4
- package/src/ai/usage.ts +2 -2
- package/src/api/admin-core-settings.ts +15 -2
- package/src/api/admin-geo.ts +54 -0
- package/src/api/admin-linux-identities.ts +6 -3
- package/src/api/admin-outgoing-mail.ts +29 -15
- package/src/api/app-approval-actions.ts +31 -7
- package/src/api/approvals-loader.ts +3 -3
- package/src/api/approvals.ts +51 -0
- package/src/api/apps.ts +4 -2
- package/src/api/auth/schemas.ts +3 -0
- package/src/api/auth.ts +54 -18
- package/src/api/capabilities.ts +258 -181
- package/src/api/capability-streams.ts +21 -13
- package/src/api/cli-plugins.ts +4 -1
- package/src/api/geo-glyphs.ts +11 -0
- package/src/api/geo.ts +128 -0
- package/src/api/help.ts +12 -4
- package/src/api/index.ts +5 -0
- package/src/api/mcp.ts +73 -41
- package/src/api/me-panel-consent.ts +8 -5
- package/src/api/me.ts +79 -3
- package/src/api/search.ts +21 -14
- package/src/api/sync-ops.ts +19 -13
- package/src/api/widgets.ts +42 -18
- package/src/approvals/Approvals.tsx +30 -66
- package/src/approvals/approval-card.ts +68 -0
- package/src/browser/CloudResourceSearch.browser-harness.tsx +53 -1
- package/src/browser/CloudResourceSearch.tsx +3 -13
- package/src/browser/PanelLeaveNotice.tsx +18 -0
- package/src/browser/navigation-search.ts +78 -25
- package/src/browser/panel-leave.ts +69 -0
- package/src/browser/panel-messages.ts +12 -0
- package/src/browser/panel-slot.ts +173 -24
- package/src/browser/panel.ts +3 -0
- package/src/browser/resource-search-messages.ts +2 -0
- package/src/capabilities/client.ts +28 -1
- package/src/capabilities/executions.ts +22 -3
- package/src/capabilities/geo-client.ts +57 -0
- package/src/capabilities/response.ts +2 -1
- package/src/capabilities/review-storage.ts +24 -0
- package/src/capabilities/reviews.ts +17 -3
- package/src/capabilities/server.ts +36 -16
- package/src/cli/access.ts +15 -23
- package/src/cli/admin/linux.ts +13 -2
- package/src/cli/admin/outgoing-mail.ts +26 -4
- package/src/cli/capabilities.ts +19 -0
- package/src/config/env.ts +0 -6
- package/src/contracts/app-access.ts +48 -0
- package/src/contracts/app-approval.ts +3 -3
- package/src/contracts/app.ts +4 -1
- package/src/contracts/approvals.ts +2 -2
- package/src/contracts/avatar.ts +186 -0
- package/src/contracts/capabilities.ts +149 -55
- package/src/contracts/geo-admin.ts +127 -0
- package/src/contracts/geo-map.ts +19 -0
- package/src/contracts/geo-route.ts +168 -0
- package/src/contracts/geo.ts +248 -0
- package/src/contracts/index.ts +4 -0
- package/src/contracts/outgoing-mail.ts +17 -4
- package/src/contracts/registry.ts +2 -0
- package/src/contracts/shared.ts +3 -0
- package/src/events/app-access.ts +68 -0
- package/src/events/live-delivery.ts +128 -0
- package/src/events/live.ts +14 -130
- package/src/geo/CloudMap.tsx +95 -0
- package/src/geo/map-messages.ts +17 -0
- package/src/geo/ui.ts +1 -0
- package/src/index.ts +2 -0
- package/src/server/client-ip.ts +36 -0
- package/src/server/index.ts +3 -4
- package/src/server/middleware/app-access.ts +312 -0
- package/src/server/middleware/auth.ts +5 -3
- package/src/server/middleware/invocation.ts +11 -0
- package/src/server/middleware/rate-limit.ts +4 -4
- package/src/server/services/access.ts +13 -9
- package/src/server/services/index.ts +0 -2
- package/src/server/services/services.ts +0 -2
- package/src/services/accounts/app.ts +23 -3
- package/src/services/accounts/posix.ts +21 -7
- package/src/services/accounts/users.ts +12 -1
- package/src/services/allowed-network-fetch.ts +16 -10
- package/src/services/app-access-metrics.ts +24 -0
- package/src/services/app-access.ts +123 -0
- package/src/services/app-discovery.ts +27 -0
- package/src/services/approvals/content.ts +40 -0
- package/src/services/approvals/device.ts +49 -33
- package/src/services/approvals/index.ts +15 -6
- package/src/services/approvals/list.ts +6 -11
- package/src/services/approvals/question.ts +72 -0
- package/src/services/audit/index.ts +1 -0
- package/src/services/auth-flows/ipa.ts +39 -2
- package/src/services/auth-flows/password-reset.ts +32 -6
- package/src/services/downstream-budget.ts +87 -0
- package/src/services/geo/admin.ts +262 -0
- package/src/services/geo/bucket.ts +67 -0
- package/src/services/geo/cache.ts +57 -0
- package/src/services/geo/costs.ts +5 -0
- package/src/services/geo/errors.ts +41 -0
- package/src/services/geo/fixtures/README.md +17 -0
- package/src/services/geo/fixtures/along.json +355 -0
- package/src/services/geo/fixtures/autocomplete.json +218 -0
- package/src/services/geo/fixtures/geocode.json +129 -0
- package/src/services/geo/fixtures/places.json +355 -0
- package/src/services/geo/fixtures/reverse.json +61 -0
- package/src/services/geo/fixtures/routing.json +57 -0
- package/src/services/geo/geoapify-route.ts +93 -0
- package/src/services/geo/geoapify.ts +281 -0
- package/src/services/geo/ledger.ts +122 -0
- package/src/services/geo/map.ts +160 -0
- package/src/services/geo/migrate.ts +25 -0
- package/src/services/geo/polyline.ts +184 -0
- package/src/services/geo/route-chain.ts +145 -0
- package/src/services/geo/route-costs.ts +9 -0
- package/src/services/geo/route-geometry.ts +39 -0
- package/src/services/geo/route-plan.ts +172 -0
- package/src/services/geo/route-service.ts +433 -0
- package/src/services/geo/service.ts +300 -0
- package/src/services/geo/settings.ts +49 -0
- package/src/services/help/index.ts +16 -7
- package/src/services/help/types.ts +6 -1
- package/src/services/identity/invocation-actor.ts +20 -2
- package/src/services/identity/issue-invocation.ts +67 -0
- package/src/services/identity/key-ring.ts +5 -3
- package/src/services/identity/runtime-config.ts +23 -0
- package/src/services/index.ts +11 -0
- package/src/services/mandates/index.ts +40 -3
- package/src/services/network-security.ts +6 -3
- package/src/services/network-transport.ts +99 -0
- package/src/services/notifications/catalog.ts +3 -2
- package/src/services/notifications/runtime.ts +1 -0
- package/src/services/notifications/web-push-transport.ts +45 -38
- package/src/services/outgoing-mail/dispatcher.ts +6 -1
- package/src/services/outgoing-mail/index.ts +55 -1
- package/src/services/outgoing-mail/messages.ts +82 -3
- package/src/services/outgoing-mail/resolve-host.ts +2 -2
- package/src/services/outgoing-mail/retention.ts +13 -1
- package/src/services/outgoing-mail/store.ts +36 -6
- package/src/services/outgoing-mail/test-send.ts +34 -4
- package/src/services/outgoing-mail/transport.ts +1 -1
- package/src/services/pdf/gotenberg.ts +11 -38
- package/src/services/pdf/markdown.ts +160 -2
- package/src/services/pdf/template-preview.ts +1 -1
- package/src/services/public-http.ts +28 -40
- package/src/services/session/index.ts +203 -29
- package/src/services/settings/app.ts +4 -1
- package/src/services/settings/core-settings.ts +88 -0
- package/src/services/settings/defaults.ts +8 -2
- package/src/services/settings/errors.ts +11 -0
- package/src/services/settings/geo-budget.ts +40 -0
- package/src/services/settings/index.ts +4 -3
- package/src/services/settings/store.ts +25 -17
- package/src/services/weather/geo.ts +29 -111
- package/src/services/weather/index.ts +4 -3
- package/src/services/weather/locations.ts +8 -5
- package/src/services/weather/migrate.ts +1 -0
- package/src/services/weather/settings.ts +0 -16
- package/src/shared/__snapshots__/avatar-art.test.ts.snap +5 -0
- package/src/shared/avatar-art.ts +400 -0
- package/src/shared/index.ts +2 -0
- package/src/shared/network-address.ts +22 -3
- package/src/shared/template-rendering.ts +36 -4
- package/src/shared-text/documents.ts +1 -1
- package/src/shared-text/index.ts +2 -0
- package/src/shared-text/log.ts +51 -3
- package/src/shared-text/snapshots.ts +340 -0
- package/src/ssr/AdminLayout.tsx +4 -2
- package/src/ssr/CloudShell.island.tsx +6 -0
- 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 +65 -15
- package/src/ssr/LayoutHelp.browser-harness.tsx +1 -1
- package/src/ssr/LayoutHelp.tsx +185 -542
- package/src/ssr/LayoutHelpPanel.browser-harness.tsx +6 -7
- package/src/ssr/LayoutHelpPanel.server.browser-harness.tsx +131 -0
- package/src/ssr/LayoutHelpPanel.server.tsx +115 -0
- package/src/ssr/PanelRuntime.island.tsx +11 -7
- package/src/ssr/PwaLayout.tsx +2 -2
- package/src/ssr/RailApps.tsx +6 -0
- package/src/ssr/ShellRuntime.island.tsx +23 -7
- package/src/ssr/__snapshots__/Layout.unchanged.render.test.tsx.snap +151 -0
- package/src/ssr/__snapshots__/LayoutHelp.ssr.render.test.tsx.snap +29 -0
- package/src/ssr/admin-navigation.ts +1 -0
- package/src/ssr/app-badges.ts +6 -1
- package/src/ssr/compact-shell.browser-harness.ts +44 -5
- package/src/ssr/help.ts +7 -5
- package/src/ssr/index.ts +2 -1
- package/src/ssr/islands/index.ts +3 -4
- package/src/ssr/layout-context.ts +2 -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-views.tsx +484 -0
- package/src/ssr/mobile-menu-history.ts +4 -0
- package/src/ssr/page-swap-fixture/Column.island.tsx +15 -0
- package/src/ssr/page-swap-fixture/ProviderProbe.island.tsx +31 -0
- package/src/ssr/page-swap.browser-harness.tsx +177 -0
- package/src/ssr/page-swap.ts +28 -0
- package/src/ssr/page-trail-runtime.ts +32 -13
- package/src/ssr/panel-consent.ts +2 -2
- package/src/ssr/panel-idle.ts +9 -0
- package/src/ssr/panel-slot.browser-harness.tsx +3 -0
- package/src/ssr/platform-messages.ts +8 -0
- package/src/ssr/runtime.ts +17 -0
- package/src/ssr/shell-data.tsx +10 -2
- package/src/styles/approvals.css +6 -0
- package/src/styles/effects.css +15 -0
- package/src/styles/global.css +70 -6
- package/src/server/services/geo.ts +0 -215
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { Combobox, type ComboboxOption, useLocale } from "@k2b/ui";
|
|
2
2
|
import type { Principal, ServiceAccountKind } from "../contracts/shared";
|
|
3
|
-
import { groupDisplayName } from "../shared/account-display";
|
|
4
3
|
import { accessMessages } from "./messages";
|
|
4
|
+
import { principalLabel } from "./principal-label";
|
|
5
5
|
import { serviceAccountKindDisplay } from "./service-account-kind";
|
|
6
6
|
|
|
7
7
|
export const principalKey = (p: Principal) =>
|
|
@@ -13,7 +13,7 @@ export const principalKey = (p: Principal) =>
|
|
|
13
13
|
? `s:${p.serviceAccountId}`
|
|
14
14
|
: p.type;
|
|
15
15
|
type Entity =
|
|
16
|
-
| { kind: "user"; user: { id: string; displayName: string; uid: string; mail: string | null } }
|
|
16
|
+
| { kind: "user"; user: { id: string; displayName: string; uid: string; mail: string | null; avatarHash?: string | null } }
|
|
17
17
|
| { kind: "group"; group: { id: string; name: string; description: string | null } }
|
|
18
18
|
| {
|
|
19
19
|
kind: "service_account";
|
|
@@ -35,17 +35,36 @@ export default function PrincipalPicker(props: {
|
|
|
35
35
|
disabled?: boolean;
|
|
36
36
|
readOnly?: boolean;
|
|
37
37
|
/** `serviceAccountKind` is set for service accounts so a new row can show the same kind as the result. */
|
|
38
|
-
onSelect: (
|
|
38
|
+
onSelect: (
|
|
39
|
+
principal: Principal,
|
|
40
|
+
display: { displayName: string; avatarHash?: string | null; serviceAccountKind?: ServiceAccountKind },
|
|
41
|
+
) => void;
|
|
39
42
|
}) {
|
|
40
43
|
const locale = useLocale(),
|
|
41
44
|
t = () => accessMessages.resolve([locale()]).t;
|
|
42
|
-
type Entry = {
|
|
45
|
+
type Entry = {
|
|
46
|
+
principal: Principal;
|
|
47
|
+
label: string;
|
|
48
|
+
icon: string;
|
|
49
|
+
description?: string;
|
|
50
|
+
avatarHash?: string | null;
|
|
51
|
+
serviceAccountKind?: ServiceAccountKind;
|
|
52
|
+
};
|
|
43
53
|
let selectable = new Map<string, Entry>();
|
|
44
54
|
const load = async (search: string, signal: AbortSignal): Promise<ComboboxOption[]> => {
|
|
45
55
|
const entries: Entry[] = [];
|
|
46
56
|
if (props.allowAuthenticated !== false)
|
|
47
|
-
entries.push({
|
|
48
|
-
|
|
57
|
+
entries.push({
|
|
58
|
+
principal: { type: "authenticated" },
|
|
59
|
+
label: principalLabel({ principal: { type: "authenticated" } }, locale()),
|
|
60
|
+
icon: "ti ti-lock-open-2",
|
|
61
|
+
});
|
|
62
|
+
if (props.allowPublic)
|
|
63
|
+
entries.push({
|
|
64
|
+
principal: { type: "public" },
|
|
65
|
+
label: principalLabel({ principal: { type: "public" } }, locale()),
|
|
66
|
+
icon: "ti ti-world",
|
|
67
|
+
});
|
|
49
68
|
if (search.length >= 2) {
|
|
50
69
|
const url = new URL("/api/accounts/entities", window.location.origin);
|
|
51
70
|
url.searchParams.set("search", search);
|
|
@@ -64,14 +83,18 @@ export default function PrincipalPicker(props: {
|
|
|
64
83
|
if (e.kind === "user")
|
|
65
84
|
entries.push({
|
|
66
85
|
principal: { type: "user", userId: e.user.id },
|
|
67
|
-
label:
|
|
86
|
+
label: principalLabel(
|
|
87
|
+
{ principal: { type: "user", userId: e.user.id }, displayName: e.user.displayName || e.user.uid },
|
|
88
|
+
locale(),
|
|
89
|
+
),
|
|
90
|
+
avatarHash: e.user.avatarHash,
|
|
68
91
|
icon: "ti ti-user",
|
|
69
92
|
description: e.user.mail ?? e.user.uid,
|
|
70
93
|
});
|
|
71
94
|
else if (e.kind === "group")
|
|
72
95
|
entries.push({
|
|
73
96
|
principal: { type: "group", groupId: e.group.id },
|
|
74
|
-
label:
|
|
97
|
+
label: principalLabel({ principal: { type: "group", groupId: e.group.id }, displayName: e.group.name }, locale()),
|
|
75
98
|
icon: "ti ti-users-group",
|
|
76
99
|
description: e.group.description ?? undefined,
|
|
77
100
|
});
|
|
@@ -80,7 +103,14 @@ export default function PrincipalPicker(props: {
|
|
|
80
103
|
const kind = serviceAccountKindDisplay(account.kind, t());
|
|
81
104
|
entries.push({
|
|
82
105
|
principal: { type: "service_account", serviceAccountId: account.id },
|
|
83
|
-
label:
|
|
106
|
+
label: principalLabel(
|
|
107
|
+
{
|
|
108
|
+
principal: { type: "service_account", serviceAccountId: account.id },
|
|
109
|
+
displayName: account.name,
|
|
110
|
+
serviceAccountKind: account.kind,
|
|
111
|
+
},
|
|
112
|
+
locale(),
|
|
113
|
+
),
|
|
84
114
|
// Resource-bound accounts often share a name; their binding tells them apart.
|
|
85
115
|
description:
|
|
86
116
|
account.kind === "resource_bound"
|
|
@@ -114,7 +144,12 @@ export default function PrincipalPicker(props: {
|
|
|
114
144
|
fetchData={load}
|
|
115
145
|
onSelect={(option) => {
|
|
116
146
|
const entry = selectable.get(option.id);
|
|
117
|
-
if (entry)
|
|
147
|
+
if (entry)
|
|
148
|
+
props.onSelect(entry.principal, {
|
|
149
|
+
displayName: entry.label,
|
|
150
|
+
avatarHash: entry.avatarHash,
|
|
151
|
+
serviceAccountKind: entry.serviceAccountKind,
|
|
152
|
+
});
|
|
118
153
|
}}
|
|
119
154
|
/>
|
|
120
155
|
);
|
package/src/access/messages.ts
CHANGED
|
@@ -8,6 +8,10 @@ export const accessMessages = i18n.define({
|
|
|
8
8
|
edit: "Edit",
|
|
9
9
|
manage: "Manage",
|
|
10
10
|
noAccess: "No access",
|
|
11
|
+
unknownPerson: "Unknown person",
|
|
12
|
+
deletedGroup: "Deleted group",
|
|
13
|
+
unknownServiceAccount: "Unknown service account",
|
|
14
|
+
unknownAgent: "Unknown agent",
|
|
11
15
|
allUsers: "All users (including guests)",
|
|
12
16
|
public: "Public",
|
|
13
17
|
signedInDescription: "Anyone signed in to Cloud",
|
|
@@ -87,6 +91,10 @@ export const accessMessages = i18n.define({
|
|
|
87
91
|
edit: "Bearbeiten",
|
|
88
92
|
manage: "Verwalten",
|
|
89
93
|
noAccess: "Kein Zugriff",
|
|
94
|
+
unknownPerson: "Unbekannte Person",
|
|
95
|
+
deletedGroup: "Gelöschte Gruppe",
|
|
96
|
+
unknownServiceAccount: "Unbekanntes Dienstkonto",
|
|
97
|
+
unknownAgent: "Unbekannter Agent",
|
|
90
98
|
allUsers: "Alle Benutzer einschließlich Gäste",
|
|
91
99
|
public: "Öffentlich",
|
|
92
100
|
signedInDescription: "Alle bei Cloud angemeldeten Personen",
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import type { Principal, ServiceAccountKind } from "../contracts/shared";
|
|
2
|
+
import { groupDisplayName } from "../shared/account-display";
|
|
3
|
+
import { accessMessages } from "./messages";
|
|
4
|
+
|
|
5
|
+
/** A human label, never an identity. Resolve names only after authorizing the resource operation. */
|
|
6
|
+
export const principalLabel = (
|
|
7
|
+
entry: { principal: Principal; displayName?: string | null; serviceAccountKind?: ServiceAccountKind },
|
|
8
|
+
locale: string | null | undefined,
|
|
9
|
+
): string => {
|
|
10
|
+
const { t } = accessMessages.resolve([locale ?? "en"]);
|
|
11
|
+
const { principal, displayName } = entry;
|
|
12
|
+
if (principal.type === "authenticated") return t.allUsers;
|
|
13
|
+
if (principal.type === "public") return t.public;
|
|
14
|
+
if (displayName) return principal.type === "group" ? groupDisplayName(displayName, locale) : displayName;
|
|
15
|
+
if (principal.type === "user") return t.unknownPerson;
|
|
16
|
+
if (principal.type === "group") return t.deletedGroup;
|
|
17
|
+
return entry.serviceAccountKind === "agent" ? t.unknownAgent : t.unknownServiceAccount;
|
|
18
|
+
};
|
package/src/access/ui.ts
CHANGED
|
@@ -10,6 +10,7 @@ export { isManagerEntry, type ManagerCandidate } from "./managers";
|
|
|
10
10
|
export type { AllowedLevel, GrantableLevel } from "./PermissionEditor";
|
|
11
11
|
export { default as PermissionEditor } from "./PermissionEditor";
|
|
12
12
|
export { default as PrincipalPicker, principalKey } from "./PrincipalPicker";
|
|
13
|
+
export { principalLabel } from "./principal-label";
|
|
13
14
|
export type {
|
|
14
15
|
ResourceApiKey,
|
|
15
16
|
ResourceApiKeyPermissionOption,
|
|
@@ -3,6 +3,7 @@ import { Button, ScrollArea, TextInput, useLocale } from "@k2b/ui";
|
|
|
3
3
|
import type { JSX } from "solid-js";
|
|
4
4
|
import { createSignal, For, Show } from "solid-js";
|
|
5
5
|
import { accessMessages } from "../access/messages";
|
|
6
|
+
import { principalLabel } from "../access/principal-label";
|
|
6
7
|
import { serviceAccountKindDisplay } from "../access/service-account-kind";
|
|
7
8
|
import { groupDisplayName } from "../shared/account-display";
|
|
8
9
|
import { CloudAvatar } from "./Avatar";
|
|
@@ -233,8 +234,8 @@ const EntitySearch = (props: EntitySearchProps) => {
|
|
|
233
234
|
<Show when={props.includeAuthenticated}>
|
|
234
235
|
<ResultRow
|
|
235
236
|
icon="ti-lock-open-2"
|
|
236
|
-
title="
|
|
237
|
-
subtitle=
|
|
237
|
+
title={principalLabel({ principal: { type: "authenticated" } }, locale())}
|
|
238
|
+
subtitle={accessMessages.resolve([locale()]).t.signedInDescription}
|
|
238
239
|
disabled={props.disabled}
|
|
239
240
|
onSelect={() => props.onSelect({ type: "authenticated" })}
|
|
240
241
|
/>
|
|
@@ -242,8 +243,8 @@ const EntitySearch = (props: EntitySearchProps) => {
|
|
|
242
243
|
<Show when={props.includePublic}>
|
|
243
244
|
<ResultRow
|
|
244
245
|
icon="ti-world"
|
|
245
|
-
title="
|
|
246
|
-
subtitle=
|
|
246
|
+
title={principalLabel({ principal: { type: "public" } }, locale())}
|
|
247
|
+
subtitle={accessMessages.resolve([locale()]).t.publicDescription}
|
|
247
248
|
disabled={props.disabled}
|
|
248
249
|
onSelect={() => props.onSelect({ type: "public" })}
|
|
249
250
|
/>
|
package/src/ai/action-review.ts
CHANGED
|
@@ -4,6 +4,7 @@ import type {
|
|
|
4
4
|
CapabilityActionReceipt,
|
|
5
5
|
CapabilityActionReview,
|
|
6
6
|
CapabilityActionReviewData,
|
|
7
|
+
CapabilityReviewContentRef,
|
|
7
8
|
CapabilitySemanticLink,
|
|
8
9
|
} from "../contracts/capabilities";
|
|
9
10
|
|
|
@@ -32,7 +33,7 @@ export type AiLegacyActionReview = {
|
|
|
32
33
|
};
|
|
33
34
|
|
|
34
35
|
/** A new review, with `ask` filled from the declared policy when the call itself names no reason. */
|
|
35
|
-
export type AiStoredActionReview = CapabilityActionCallReview | AiLegacyActionReview;
|
|
36
|
+
export type AiStoredActionReview = (CapabilityActionCallReview & { contentRef?: CapabilityReviewContentRef }) | AiLegacyActionReview;
|
|
36
37
|
|
|
37
38
|
export const isLegacyActionReview = (review: AiStoredActionReview): review is AiLegacyActionReview => "legacy" in review;
|
|
38
39
|
|
|
@@ -79,7 +79,7 @@ const reconcileApp = async (app: AppRegistryEntry, tx: TransactionSQL): Promise<
|
|
|
79
79
|
{ skills: unknown }[]
|
|
80
80
|
>`SELECT skills FROM ai.app_skill_catalogs WHERE app_id=${app.id} AND manifest_hash=${manifest}`;
|
|
81
81
|
if (!catalog) return false; // Publication may still be in flight: keep the last known state.
|
|
82
|
-
const roles = appAudienceRoles(app.nav);
|
|
82
|
+
const roles = app.access === undefined ? appAudienceRoles(app.nav) : undefined;
|
|
83
83
|
const roleArray = roles === undefined ? null : toPgTextArray([...roles]);
|
|
84
84
|
const [state] = await tx<{ current: boolean }[]>`SELECT
|
|
85
85
|
EXISTS(SELECT 1 FROM ai.app_skills WHERE app_id=${app.id}) AND
|
package/src/ai/capabilities.ts
CHANGED
|
@@ -12,6 +12,7 @@ import {
|
|
|
12
12
|
capabilityApprovalText,
|
|
13
13
|
} from "../_internal/capability-sentences";
|
|
14
14
|
import { HELP_READ_MAX_CHARS, HELP_SEARCH_MAX_LIMIT, readHelpArticle } from "../_internal/help-catalog";
|
|
15
|
+
import type { listApps } from "../_internal/registry";
|
|
15
16
|
import {
|
|
16
17
|
type CapabilityActionManifest,
|
|
17
18
|
type CapabilityActionReviewData,
|
|
@@ -23,6 +24,8 @@ import {
|
|
|
23
24
|
} from "../contracts/capabilities";
|
|
24
25
|
import type { CapabilityRegistryEntry } from "../contracts/registry";
|
|
25
26
|
import type { RequestActor } from "../server";
|
|
27
|
+
import type { RequestAuthority } from "../server/middleware/auth";
|
|
28
|
+
import { discoverableAppEntries } from "../services/app-discovery";
|
|
26
29
|
import type { HelpReader, HelpReaderFactory } from "../services/help";
|
|
27
30
|
import { type MandatePolicyV1, mandatePolicyCanPermitCapability } from "../services/mandates/policy";
|
|
28
31
|
import { resolveAppIdentityPresentation } from "../shared/app-presentation";
|
|
@@ -869,6 +872,7 @@ export const createAiToolResolver =
|
|
|
869
872
|
(input: {
|
|
870
873
|
conversationId: string;
|
|
871
874
|
actor: RequestActor;
|
|
875
|
+
authority?: RequestAuthority | null;
|
|
872
876
|
staticTools: AiRuntimeTool[];
|
|
873
877
|
allowedTools?: readonly string[] | null;
|
|
874
878
|
/** Undefined for interactive runs; null fails closed when a background mandate cannot be loaded. */
|
|
@@ -880,6 +884,7 @@ export const createAiToolResolver =
|
|
|
880
884
|
runtimeContext?: Omit<AiToolPreparationContext, "actor" | "conversationId">;
|
|
881
885
|
store: Pick<AiConversationService, "getLoadedTools" | "loadTools">;
|
|
882
886
|
listRegistry?: () => Promise<CapabilityRegistryEntry[]>;
|
|
887
|
+
listApps?: typeof listApps;
|
|
883
888
|
onCapabilityRegistryError?: (error: unknown) => void;
|
|
884
889
|
help?: HelpReaderFactory;
|
|
885
890
|
locale?: string;
|
|
@@ -904,7 +909,17 @@ export const createAiToolResolver =
|
|
|
904
909
|
input.listRegistry ? resolveCapabilityRegistry(input.listRegistry, input.onCapabilityRegistryError) : [],
|
|
905
910
|
input.store.getLoadedTools({ conversationId: input.conversationId }),
|
|
906
911
|
]);
|
|
907
|
-
const registry =
|
|
912
|
+
const registry =
|
|
913
|
+
(await resolveCapabilityRegistry(
|
|
914
|
+
() => discoverableAppEntries(input.authority ?? null, liveRegistry ?? [], input.listApps),
|
|
915
|
+
input.onCapabilityRegistryError,
|
|
916
|
+
)) ?? [];
|
|
917
|
+
const visibleAppIds = new Set(registry.map((app) => app.appId));
|
|
918
|
+
const hiddenNames = new Set(
|
|
919
|
+
buildAiCapabilityCatalog(liveRegistry ?? [])
|
|
920
|
+
.filter((entry) => !visibleAppIds.has(entry.appId))
|
|
921
|
+
.flatMap((entry) => [entry.name, entry.providerName]),
|
|
922
|
+
);
|
|
908
923
|
const configuredLimit = Math.floor(input.maxLoadedTools ?? 0);
|
|
909
924
|
const loadedNames = configuredLimit > 0 ? persistedLoadedNames.slice(-configuredLimit) : persistedLoadedNames;
|
|
910
925
|
if (loadedNames.length !== persistedLoadedNames.length) {
|
|
@@ -931,7 +946,9 @@ export const createAiToolResolver =
|
|
|
931
946
|
);
|
|
932
947
|
const capabilityCatalog = fullCapabilityCatalog.filter((entry) => (!allowed || allowed.has(entry.name)) && mandateAllows(entry));
|
|
933
948
|
// The turn's actor decides which apps' Help exists for the model, like every other Help surface.
|
|
934
|
-
const helpTools = input.help
|
|
949
|
+
const helpTools = input.help
|
|
950
|
+
? createAiHelpTools(input.help(input.locale ?? "en", input.actor, { authority: input.authority ?? null }))
|
|
951
|
+
: [];
|
|
935
952
|
const resourceTool =
|
|
936
953
|
input.execute && (capabilityCatalog.length > 0 || input.staticTools.some((tool) => tool.def.name === "code_read"))
|
|
937
954
|
? createAiResourceReaderTool({ apps: registry, catalog: capabilityCatalog, execute: input.execute })
|
|
@@ -940,7 +957,7 @@ export const createAiToolResolver =
|
|
|
940
957
|
const builtIns = allBuiltIns.filter((tool) => !allowed || allowed.has(tool.def.name));
|
|
941
958
|
const catalog = buildAiToolCatalog(builtIns, capabilityCatalog);
|
|
942
959
|
const catalogNames = new Set(catalog.map((entry) => entry.name));
|
|
943
|
-
const unavailableLoadedNames = loadedNames.filter((name) => !catalogNames.has(name));
|
|
960
|
+
const unavailableLoadedNames = loadedNames.filter((name) => !catalogNames.has(name) && !hiddenNames.has(name));
|
|
944
961
|
const outOfScope = new Set([
|
|
945
962
|
...allBuiltIns.map((tool) => tool.def.name),
|
|
946
963
|
...fullCapabilityCatalog.flatMap((entry) => [entry.name, entry.providerName]),
|
|
@@ -959,6 +976,7 @@ export const createAiToolResolver =
|
|
|
959
976
|
const unavailableReason = (requested: string): AiToolUnavailableReason => {
|
|
960
977
|
if (input.skillNames?.includes(requested)) return "skill";
|
|
961
978
|
const name = loadedByProviderName.get(requested) ?? requested;
|
|
979
|
+
if (hiddenNames.has(requested) || hiddenNames.has(name)) return "unknown";
|
|
962
980
|
if (mandateExcluded.has(name)) return "not_allowed";
|
|
963
981
|
if (allowed && outOfScope.has(name) && !allowed.has(name)) return "not_allowed";
|
|
964
982
|
if (unoffered.has(name)) return "not_offered_in_turn";
|
|
@@ -10,8 +10,7 @@ import {
|
|
|
10
10
|
type CapabilityActionReviewData,
|
|
11
11
|
CapabilityActionReviewDataSchema,
|
|
12
12
|
} from "../contracts/capabilities";
|
|
13
|
-
import type { RequestActor } from "../server";
|
|
14
|
-
import type { AccessSubject } from "../server/services/access";
|
|
13
|
+
import type { RequestActor, RequestAuthority } from "../server";
|
|
15
14
|
import { isAccountCategoryAllowed } from "../services/account-category-policy";
|
|
16
15
|
import { isAccountExpired } from "../services/account-model";
|
|
17
16
|
import { accounts } from "../services/accounts";
|
|
@@ -26,13 +25,14 @@ import type { AiConversationService } from "./types";
|
|
|
26
25
|
export { AiCapabilityExecutionError, aiCapabilityCheckResult } from "./capability-result";
|
|
27
26
|
|
|
28
27
|
type CapabilityActor = Extract<RequestActor, { kind: "user" }>;
|
|
28
|
+
type AiCapabilityAuthority = RequestAuthority & { actor: CapabilityActor };
|
|
29
29
|
|
|
30
30
|
export const resolveAiCapabilityActor = async (input: {
|
|
31
31
|
conversationId: string;
|
|
32
32
|
persistedActor?: RequestActor;
|
|
33
33
|
store: Pick<AiConversationService, "getConversation">;
|
|
34
34
|
getUser?: typeof accounts.users.get;
|
|
35
|
-
}): Promise<
|
|
35
|
+
}): Promise<AiCapabilityAuthority> => {
|
|
36
36
|
if (input.persistedActor?.kind !== "user") throw new Error("Cloud capabilities require a current user-backed actor.");
|
|
37
37
|
const conversation = await input.store.getConversation({ conversationId: input.conversationId });
|
|
38
38
|
if (!conversation?.createdByUserId || conversation.createdByUserId !== input.persistedActor.user.id) {
|
|
@@ -44,9 +44,16 @@ export const resolveAiCapabilityActor = async (input: {
|
|
|
44
44
|
if (!(await isAccountCategoryAllowed(user))) throw new Error("Cloud capability actor category is disabled.");
|
|
45
45
|
// A turn started in the mobile app keeps its limits: no administrator role, and invocations say so.
|
|
46
46
|
if (input.persistedActor.sessionKind === "app") {
|
|
47
|
-
return {
|
|
47
|
+
return {
|
|
48
|
+
actor: { kind: "user", user: withoutAdminRole(user), sessionKind: "app" },
|
|
49
|
+
accessSubject: { type: "user", userId: user.id },
|
|
50
|
+
credentialKind: "session",
|
|
51
|
+
scopes: [],
|
|
52
|
+
};
|
|
48
53
|
}
|
|
49
|
-
|
|
54
|
+
// Assistant persists a user actor, not a reusable transport credential. Its supported
|
|
55
|
+
// delegation is the current conversation owner's session authority.
|
|
56
|
+
return { actor: { kind: "user", user }, accessSubject: { type: "user", userId: user.id }, credentialKind: "session", scopes: [] };
|
|
50
57
|
};
|
|
51
58
|
|
|
52
59
|
const idempotencyKey = (conversationId: string, callId: string): string =>
|
|
@@ -83,7 +90,7 @@ const parseCapabilityResponse = async (response: Response, dispatched: boolean):
|
|
|
83
90
|
type AiCapabilityCall = {
|
|
84
91
|
conversationId: string;
|
|
85
92
|
turnId?: string;
|
|
86
|
-
authority:
|
|
93
|
+
authority: AiCapabilityAuthority;
|
|
87
94
|
locale?: string;
|
|
88
95
|
entry: AiCapabilityCatalogEntry;
|
|
89
96
|
args: unknown;
|
|
@@ -140,12 +147,7 @@ const dispatchAiCapability = async (
|
|
|
140
147
|
appId: input.entry.appId,
|
|
141
148
|
capabilityId: input.entry.operation.localId,
|
|
142
149
|
input: input.args,
|
|
143
|
-
authority:
|
|
144
|
-
actor: input.authority.actor,
|
|
145
|
-
accessSubject: input.authority.accessSubject,
|
|
146
|
-
credentialKind: "session",
|
|
147
|
-
scopes: [],
|
|
148
|
-
},
|
|
150
|
+
authority: input.authority,
|
|
149
151
|
mandate: input.mandate
|
|
150
152
|
? {
|
|
151
153
|
mandateId: input.mandate.id,
|
|
@@ -1,10 +1,12 @@
|
|
|
1
1
|
import {
|
|
2
2
|
type CapabilitySentencePart,
|
|
3
3
|
capabilityAskReasonText,
|
|
4
|
+
capabilityPersonText,
|
|
4
5
|
capabilityQuestionText,
|
|
5
6
|
capabilityReceiptParts,
|
|
6
7
|
capabilityReviewSentence,
|
|
7
8
|
capabilityReviewSentenceParts,
|
|
9
|
+
capabilitySentenceParts,
|
|
8
10
|
capabilitySentenceText,
|
|
9
11
|
} from "../../_internal/capability-sentences";
|
|
10
12
|
import type {
|
|
@@ -14,6 +16,7 @@ import type {
|
|
|
14
16
|
CapabilityAskReason,
|
|
15
17
|
CapabilityConsequence,
|
|
16
18
|
CapabilityPreview,
|
|
19
|
+
CapabilityReviewContentRef,
|
|
17
20
|
CapabilitySemanticLink,
|
|
18
21
|
CapabilitySlot,
|
|
19
22
|
} from "../../contracts/capabilities";
|
|
@@ -55,11 +58,13 @@ export type ActionPreviewModel =
|
|
|
55
58
|
locale: string;
|
|
56
59
|
timeZone: string;
|
|
57
60
|
preview?: CapabilityPreview;
|
|
58
|
-
|
|
61
|
+
/** Original slots for plain rule sentences, retaining people’s addresses. */
|
|
62
|
+
slots?: CapabilityActionCallReview["slots"];
|
|
63
|
+
consequences: { text: ActionText; plain: string; mark?: "added" | "removed" }[];
|
|
59
64
|
/** The rule sentence of a `rule` preview, with its chips. */
|
|
60
65
|
when?: ActionText;
|
|
61
66
|
/** Each `may` line of a `rule` preview. */
|
|
62
|
-
may?: { text: ActionText; mark?: "added" | "removed" }[];
|
|
67
|
+
may?: { text: ActionText; plain: string; mark?: "added" | "removed" }[];
|
|
63
68
|
}
|
|
64
69
|
| { kind: "legacy"; rows: AiLegacyReviewRow[]; markdown?: string; input?: unknown };
|
|
65
70
|
|
|
@@ -74,6 +79,7 @@ export type ActionCard = {
|
|
|
74
79
|
preview: ActionPreviewModel | null;
|
|
75
80
|
/** Opens exactly this version of the thing. */
|
|
76
81
|
readMore?: string;
|
|
82
|
+
contentRef?: CapabilityReviewContentRef;
|
|
77
83
|
/** The review's other links, such as editing the draft, each with its words. */
|
|
78
84
|
links: { href: string; label: string }[];
|
|
79
85
|
/** Where the thing itself opens, for an outcome Cloud could not see. */
|
|
@@ -96,6 +102,16 @@ export type ActionCard = {
|
|
|
96
102
|
hasCheck: boolean;
|
|
97
103
|
/** The receipt text of an old review's success summary. */
|
|
98
104
|
summary?: string;
|
|
105
|
+
/**
|
|
106
|
+
* For plain text: every value a new review's slots stand for (each person with address, title, text, time, count),
|
|
107
|
+
* also in consequences and rule lines, and whether the app left people out. Plain text that does not show each value
|
|
108
|
+
* whole is incomplete.
|
|
109
|
+
*/
|
|
110
|
+
slotValues?: {
|
|
111
|
+
values: string[];
|
|
112
|
+
times: { text: string; start: string; end?: string; format: "date" | "date-time" }[];
|
|
113
|
+
unlisted: boolean;
|
|
114
|
+
};
|
|
99
115
|
};
|
|
100
116
|
|
|
101
117
|
const REASON_LOOK: Record<CapabilityAskReason, Omit<ActionReason, "code" | "text">> = {
|
|
@@ -174,6 +190,19 @@ export const actionPlainText = (text: ActionText): string =>
|
|
|
174
190
|
})
|
|
175
191
|
.join("");
|
|
176
192
|
|
|
193
|
+
/** The short template, else the action sentence, read as the card's chips without person addresses or title quotes. */
|
|
194
|
+
export const reviewShortText = (review: CapabilityActionCallReview, context: ActionCardContext): string =>
|
|
195
|
+
actionPlainText(
|
|
196
|
+
partsText(
|
|
197
|
+
capabilitySentenceParts(review.sentence.short ?? review.sentence.action, review.slots, {
|
|
198
|
+
locale: review.locale,
|
|
199
|
+
timeZone: context.timeZone,
|
|
200
|
+
}),
|
|
201
|
+
review.locale,
|
|
202
|
+
context.app,
|
|
203
|
+
),
|
|
204
|
+
);
|
|
205
|
+
|
|
177
206
|
const firstLink = (links: readonly CapabilitySemanticLink[] | undefined, rels: CapabilitySemanticLink["rel"][]) =>
|
|
178
207
|
rels.map((rel) => links?.find((link) => link.rel === rel)?.href).find(Boolean);
|
|
179
208
|
|
|
@@ -192,7 +221,38 @@ const firstObjectHref = (review: CapabilityActionCallReview): string | undefined
|
|
|
192
221
|
.map((slot) => ((slot.kind === "object" || slot.kind === "place") && slot.ref ? cloudResourceHref(slot.ref) : undefined))
|
|
193
222
|
.find(Boolean);
|
|
194
223
|
|
|
195
|
-
|
|
224
|
+
const slotValues = (slots: CapabilitySlot[], context: { locale: string; timeZone: string }) => ({
|
|
225
|
+
values: slots.flatMap((slot) =>
|
|
226
|
+
slot.kind === "person"
|
|
227
|
+
? slot.people.map(capabilityPersonText)
|
|
228
|
+
: slot.kind === "object" || slot.kind === "place"
|
|
229
|
+
? [slot.title]
|
|
230
|
+
: slot.kind === "text"
|
|
231
|
+
? [slot.value]
|
|
232
|
+
: slot.kind === "count"
|
|
233
|
+
? [capabilitySentenceText("{value}", { value: slot }, context)]
|
|
234
|
+
: [],
|
|
235
|
+
),
|
|
236
|
+
// A time is shown whole as its words, or as typed rows that hold its start and end with at least its precision.
|
|
237
|
+
times: slots.flatMap((slot) =>
|
|
238
|
+
slot.kind === "time"
|
|
239
|
+
? [
|
|
240
|
+
{
|
|
241
|
+
text: capabilitySentenceText("{value}", { value: slot }, context),
|
|
242
|
+
start: slot.start,
|
|
243
|
+
...(slot.end ? { end: slot.end } : {}),
|
|
244
|
+
format: slot.format,
|
|
245
|
+
},
|
|
246
|
+
]
|
|
247
|
+
: [],
|
|
248
|
+
),
|
|
249
|
+
unlisted: slots.some((slot) => slot.kind === "person" && (slot.total ?? slot.people.length) > slot.people.length),
|
|
250
|
+
});
|
|
251
|
+
|
|
252
|
+
export const reviewActionCard = (
|
|
253
|
+
review: CapabilityActionCallReview & { contentRef?: CapabilityReviewContentRef },
|
|
254
|
+
context: ActionCardContext,
|
|
255
|
+
): ActionCard => {
|
|
196
256
|
const locale = review.locale;
|
|
197
257
|
const tz = { timeZone: context.timeZone };
|
|
198
258
|
const parts = (form: Parameters<typeof capabilityReviewSentenceParts>[1]) =>
|
|
@@ -206,6 +266,7 @@ export const reviewActionCard = (review: CapabilityActionCallReview, context: Ac
|
|
|
206
266
|
);
|
|
207
267
|
const consequence = (entry: CapabilityConsequence) => ({
|
|
208
268
|
text: sentence(entry.text, entry.slots),
|
|
269
|
+
plain: capabilitySentenceText(entry.text, entry.slots ?? review.slots, { locale, timeZone: context.timeZone }),
|
|
209
270
|
...(entry.mark ? { mark: entry.mark } : {}),
|
|
210
271
|
});
|
|
211
272
|
const code = review.ask ?? "app_asks";
|
|
@@ -224,12 +285,14 @@ export const reviewActionCard = (review: CapabilityActionCallReview, context: Ac
|
|
|
224
285
|
locale,
|
|
225
286
|
timeZone: context.timeZone,
|
|
226
287
|
...(preview ? { preview } : {}),
|
|
288
|
+
slots: review.slots,
|
|
227
289
|
consequences: (review.consequences ?? []).map(consequence),
|
|
228
290
|
...(preview?.kind === "rule"
|
|
229
291
|
? { when: sentence(preview.when.text, preview.when.slots), may: preview.may.map(consequence) }
|
|
230
292
|
: {}),
|
|
231
293
|
}
|
|
232
294
|
: null,
|
|
295
|
+
contentRef: review.contentRef,
|
|
233
296
|
readMore: firstLink(review.links, ["open", "preview"]),
|
|
234
297
|
links: otherLinks(review.links, locale, context.app.name),
|
|
235
298
|
open: firstLink(review.links, ["open", "edit", "status"]) ?? firstObjectHref(review),
|
|
@@ -255,6 +318,14 @@ export const reviewActionCard = (review: CapabilityActionCallReview, context: Ac
|
|
|
255
318
|
: plainText(context.app.name),
|
|
256
319
|
short: capabilityReviewSentence(review, "short", tz),
|
|
257
320
|
hasCheck: Boolean(review.check),
|
|
321
|
+
slotValues: slotValues(
|
|
322
|
+
[
|
|
323
|
+
review.slots,
|
|
324
|
+
...(review.consequences ?? []).map((entry) => entry.slots ?? {}),
|
|
325
|
+
...(preview?.kind === "rule" ? [preview.when.slots ?? {}, ...preview.may.map((entry) => entry.slots ?? {})] : []),
|
|
326
|
+
].flatMap((slots) => Object.values(slots)),
|
|
327
|
+
{ locale, timeZone: context.timeZone },
|
|
328
|
+
),
|
|
258
329
|
};
|
|
259
330
|
};
|
|
260
331
|
|
|
@@ -340,7 +411,8 @@ const messageRows = (block: ToolBlock): AiLegacyReviewRow[] => {
|
|
|
340
411
|
for (const line of message.split("\n").map((entry) => entry.trim())) {
|
|
341
412
|
if (!line || /^Review the validated arguments below before running this action\.$/i.test(line)) continue;
|
|
342
413
|
const heading = /^([^:]{1,40}):$/.exec(line);
|
|
343
|
-
|
|
414
|
+
// The value starts at its first visible character, so the pattern never backtracks over the white space before it.
|
|
415
|
+
const match = /^([^:]{1,40}):\s+(\S.*)$/.exec(line);
|
|
344
416
|
if (heading) {
|
|
345
417
|
open = { label: heading[1]!, value: "" };
|
|
346
418
|
rows.push(open);
|
|
@@ -36,6 +36,13 @@ const messages = i18n.define({
|
|
|
36
36
|
unavailable: "You can't answer this question here.",
|
|
37
37
|
stopping: "The response is stopping.",
|
|
38
38
|
submitFailed: "Your answer didn't go through. Try again.",
|
|
39
|
+
fullReview: "Read the complete proposal",
|
|
40
|
+
baseRevision: ({ revision }: { revision: number }) => `Based on revision ${revision}`,
|
|
41
|
+
fullReviewLoading: "Loading the complete proposal …",
|
|
42
|
+
fullReviewFailed: "The complete proposal could not be loaded. Try again before approving.",
|
|
43
|
+
fullReviewRequired: "Read the complete proposal before approving.",
|
|
44
|
+
fullReviewRetry: "Try again",
|
|
45
|
+
fullReviewClose: "Close",
|
|
39
46
|
// Preview
|
|
40
47
|
from: "From",
|
|
41
48
|
to: "To",
|
|
@@ -94,6 +101,13 @@ const messages = i18n.define({
|
|
|
94
101
|
unavailable: "Diese Frage kannst du hier nicht beantworten.",
|
|
95
102
|
stopping: "Die Antwort wird angehalten.",
|
|
96
103
|
submitFailed: "Deine Antwort ging nicht durch. Versuche es noch einmal.",
|
|
104
|
+
fullReview: "Vollständigen Vorschlag lesen",
|
|
105
|
+
baseRevision: ({ revision }: { revision: number }) => `Auf Basis von Version ${revision}`,
|
|
106
|
+
fullReviewLoading: "Der vollständige Vorschlag wird geladen …",
|
|
107
|
+
fullReviewFailed: "Der vollständige Vorschlag konnte nicht geladen werden. Versuche es vor der Freigabe noch einmal.",
|
|
108
|
+
fullReviewRequired: "Lies vor der Freigabe den vollständigen Vorschlag.",
|
|
109
|
+
fullReviewRetry: "Noch einmal versuchen",
|
|
110
|
+
fullReviewClose: "Schließen",
|
|
97
111
|
from: "Von",
|
|
98
112
|
to: "An",
|
|
99
113
|
cc: "Cc",
|
|
@@ -22,6 +22,18 @@ type ToolBlock = Extract<AiTurnBlock, { kind: "tool" }>;
|
|
|
22
22
|
const params = new URLSearchParams(location.search);
|
|
23
23
|
const lang = (params.get("lang") === "en" ? "en" : "de") as ActionFixtureLocale;
|
|
24
24
|
const fixtures = actionFixtures(lang);
|
|
25
|
+
if (params.has("full")) {
|
|
26
|
+
const block = fixtures.mail!.block;
|
|
27
|
+
if (block.review && "sentence" in block.review) {
|
|
28
|
+
block.review = {
|
|
29
|
+
...block.review,
|
|
30
|
+
contentRef: {
|
|
31
|
+
href: "/api/approvals/11111111-1111-4111-8111-111111111111/content",
|
|
32
|
+
digest: "a".repeat(64),
|
|
33
|
+
},
|
|
34
|
+
};
|
|
35
|
+
}
|
|
36
|
+
}
|
|
25
37
|
const state = params.get("state") ?? "open";
|
|
26
38
|
/** Each entry is `fixture` or `fixture:state`; its id is unique even when one fixture shows in several states. */
|
|
27
39
|
const entries = (params.get("scenarios") ?? "mail")
|