@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
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@k2b/cloud",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.42.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": {
|
|
@@ -29,6 +29,7 @@
|
|
|
29
29
|
"./desktop/solid": "./src/desktop/solid.tsx",
|
|
30
30
|
"./server": "./src/server/index.ts",
|
|
31
31
|
"./server/*": "./src/server/*",
|
|
32
|
+
"./geo/ui": "./src/geo/ui.ts",
|
|
32
33
|
"./browser": "./src/server/api-client.ts",
|
|
33
34
|
"./browser/app-approval": "./src/browser/app-approval.ts",
|
|
34
35
|
"./browser/app-badges": "./src/browser/app-badges.ts",
|
|
@@ -67,10 +68,12 @@
|
|
|
67
68
|
"./ai": "./src/ai/index.ts",
|
|
68
69
|
"./ai/admin": "./src/ai/admin.ts",
|
|
69
70
|
"./ai/browser": "./src/ai/browser.ts",
|
|
71
|
+
"./ai/question-text": "./src/ai/question-text.ts",
|
|
70
72
|
"./ai/live": "./src/ai/live.ts",
|
|
71
73
|
"./ai/live-events": "./src/ai/live-events.ts",
|
|
72
74
|
"./ai/runtime": "./src/ai/runtime.ts",
|
|
73
75
|
"./ai/solid": "./src/ai/solid.ts",
|
|
76
|
+
"./ai/testing": "./src/ai/testing.ts",
|
|
74
77
|
"./ai/tools": "./src/ai/runtime-tools.ts",
|
|
75
78
|
"./ai/ui": "./src/ai/ui.tsx",
|
|
76
79
|
"./account/ui": "./src/account/ui.ts",
|
|
@@ -107,13 +110,13 @@
|
|
|
107
110
|
"@firecrawl/anydoc": "0.2.4",
|
|
108
111
|
"@napi-rs/canvas": "1.0.9",
|
|
109
112
|
"pdfjs-dist": "6.3.289",
|
|
110
|
-
"sharp": "0.35.
|
|
113
|
+
"sharp": "0.35.5",
|
|
111
114
|
"@simplewebauthn/server": "14.0.3",
|
|
112
115
|
"@tabler/icons": "3.48.0",
|
|
113
116
|
"@tailwindcss/typography": "0.5.20",
|
|
114
117
|
"@k2b/nessi": "0.18.0",
|
|
115
|
-
"@k2b/ssr": "0.
|
|
116
|
-
"@k2b/ui": "0.
|
|
118
|
+
"@k2b/ssr": "0.20.0",
|
|
119
|
+
"@k2b/ui": "0.32.0",
|
|
117
120
|
"@k2b/stdlib": "0.30.0",
|
|
118
121
|
"@k2b/sync": "7.0.0",
|
|
119
122
|
"@nats-io/jetstream": "3.4.0",
|
|
@@ -121,6 +124,7 @@
|
|
|
121
124
|
"@modelcontextprotocol/sdk": "1.32.1",
|
|
122
125
|
"bun-plugin-tailwind": "0.1.2",
|
|
123
126
|
"hono-openapi": "1.3.3",
|
|
127
|
+
"entities": "8.0.0",
|
|
124
128
|
"htmlparser2": "12.0.0",
|
|
125
129
|
"jose": "6.2.12",
|
|
126
130
|
"katex": "0.18.9",
|
|
@@ -149,7 +153,7 @@
|
|
|
149
153
|
"babel-preset-solid": "1.9.15",
|
|
150
154
|
"hono": "4.13.9",
|
|
151
155
|
"playwright": "1.64.1",
|
|
152
|
-
"solid-js": "1.9.
|
|
156
|
+
"solid-js": "1.9.17",
|
|
153
157
|
"typescript": "7.0.2",
|
|
154
158
|
"zod": "4.6.5"
|
|
155
159
|
},
|
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
export type BoundedJsonResult = { ok: true; data: unknown } | { ok: false; reason: "too_large" | "invalid_json" };
|
|
2
2
|
|
|
3
3
|
/** Reads JSON without allowing a missing or forged Content-Length to bypass the byte cap. */
|
|
4
|
-
export const readBoundedJson = async (message: Request | Response, maxBytes: number): Promise<BoundedJsonResult> => {
|
|
4
|
+
export const readBoundedJson = async (message: Request | Response, maxBytes: number, signal?: AbortSignal): Promise<BoundedJsonResult> => {
|
|
5
|
+
signal?.throwIfAborted();
|
|
5
6
|
const declared = message.headers.get("content-length");
|
|
6
7
|
if (declared !== null) {
|
|
7
8
|
const bytes = Number(declared);
|
|
@@ -13,6 +14,12 @@ export const readBoundedJson = async (message: Request | Response, maxBytes: num
|
|
|
13
14
|
|
|
14
15
|
const reader = message.body?.getReader();
|
|
15
16
|
if (!reader) return { ok: false, reason: "invalid_json" };
|
|
17
|
+
const aborted = () => {
|
|
18
|
+
// Releasing the caller must not wait for a stalled source's cancellation hook.
|
|
19
|
+
void reader.cancel(signal?.reason).catch(() => undefined);
|
|
20
|
+
};
|
|
21
|
+
signal?.addEventListener("abort", aborted, { once: true });
|
|
22
|
+
if (signal?.aborted) aborted();
|
|
16
23
|
|
|
17
24
|
const chunks: Uint8Array[] = [];
|
|
18
25
|
let total = 0;
|
|
@@ -28,10 +35,15 @@ export const readBoundedJson = async (message: Request | Response, maxBytes: num
|
|
|
28
35
|
chunks.push(next.value);
|
|
29
36
|
}
|
|
30
37
|
} catch {
|
|
31
|
-
await reader.cancel().catch(() => undefined);
|
|
38
|
+
if (!signal?.aborted) await reader.cancel().catch(() => undefined);
|
|
32
39
|
return { ok: false, reason: "invalid_json" };
|
|
40
|
+
} finally {
|
|
41
|
+
signal?.removeEventListener("abort", aborted);
|
|
42
|
+
reader.releaseLock();
|
|
33
43
|
}
|
|
34
44
|
|
|
45
|
+
signal?.throwIfAborted();
|
|
46
|
+
|
|
35
47
|
const bytes = new Uint8Array(total);
|
|
36
48
|
let offset = 0;
|
|
37
49
|
for (const chunk of chunks) {
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { capabilityMessages } from "../shared/capability-messages";
|
|
1
2
|
import { capabilityHash, projectSchema, stableJsonValue } from "./capability-schemas";
|
|
2
3
|
|
|
3
4
|
export { capabilityHash } from "./capability-schemas";
|
|
@@ -8,6 +9,7 @@ import {
|
|
|
8
9
|
CAPABILITY_ERROR_STATUSES,
|
|
9
10
|
CAPABILITY_FRAMEWORK_ERROR_CODES,
|
|
10
11
|
CAPABILITY_MAX_RESULT_BYTES,
|
|
12
|
+
CAPABILITY_MAX_REVIEW_RESPONSE_BYTES,
|
|
11
13
|
CAPABILITY_PROTOCOL_VERSION,
|
|
12
14
|
type CapabilityActionDefinition,
|
|
13
15
|
type CapabilityActionManifest,
|
|
@@ -193,7 +195,7 @@ const providerPayload = (code: string, message: string, details?: Record<string,
|
|
|
193
195
|
/** Serializes one framework-owned provider response exactly once and enforces the public byte bound. */
|
|
194
196
|
export const serializeCapabilityProviderResult = (
|
|
195
197
|
result: CapabilityProviderResult,
|
|
196
|
-
options: { nonIdempotentAction?: boolean } = {},
|
|
198
|
+
options: { nonIdempotentAction?: boolean; review?: boolean } = {},
|
|
197
199
|
): { body: string; status: number } => {
|
|
198
200
|
if (options.nonIdempotentAction && !result.ok && result.error.code === CAPABILITY_FRAMEWORK_ERROR_CODES.invalidAppResponse) {
|
|
199
201
|
const unknown = providerPayload(
|
|
@@ -206,7 +208,9 @@ export const serializeCapabilityProviderResult = (
|
|
|
206
208
|
const payload = result.ok ? result.data : providerPayload(result.error.code, result.error.message, result.error.details);
|
|
207
209
|
try {
|
|
208
210
|
const body = JSON.stringify(payload);
|
|
209
|
-
if (
|
|
211
|
+
if (
|
|
212
|
+
new TextEncoder().encode(body).byteLength <= (options.review ? CAPABILITY_MAX_REVIEW_RESPONSE_BYTES : CAPABILITY_MAX_RESULT_BYTES)
|
|
213
|
+
) {
|
|
210
214
|
return { body, status: result.ok ? 200 : result.error.status };
|
|
211
215
|
}
|
|
212
216
|
} catch {
|
|
@@ -1091,7 +1095,48 @@ const schemaSemantics = (value: unknown): unknown => {
|
|
|
1091
1095
|
const sameSchema = (left: unknown, right: unknown): boolean =>
|
|
1092
1096
|
JSON.stringify(stableJsonValue(schemaSemantics(left))) === JSON.stringify(stableJsonValue(schemaSemantics(right)));
|
|
1093
1097
|
|
|
1098
|
+
const isSchemaObject = (value: unknown): value is JsonSchema => typeof value === "object" && value !== null && !Array.isArray(value);
|
|
1099
|
+
|
|
1100
|
+
// Input recursion visits schema nodes explicitly. Never strip annotation names from
|
|
1101
|
+
// property maps or literal enum/const values when proving that old inputs survive.
|
|
1102
|
+
const inputConstraints = (schema: unknown): unknown =>
|
|
1103
|
+
isSchemaObject(schema) ? Object.fromEntries(Object.entries(schema).filter(([key]) => key !== "title" && key !== "description")) : schema;
|
|
1104
|
+
const sameInputSchema = (left: unknown, right: unknown): boolean =>
|
|
1105
|
+
JSON.stringify(stableJsonValue(inputConstraints(left))) === JSON.stringify(stableJsonValue(inputConstraints(right)));
|
|
1106
|
+
|
|
1107
|
+
/** Only finite scalar tags: object/array equality and general schema exclusion are outside this proof. */
|
|
1108
|
+
const discriminatorValues = (schema: unknown): unknown[] | undefined => {
|
|
1109
|
+
if (!isSchemaObject(schema)) return;
|
|
1110
|
+
const values = Object.hasOwn(schema, "const") ? [schema.const] : schema.enum;
|
|
1111
|
+
if (
|
|
1112
|
+
Array.isArray(values) &&
|
|
1113
|
+
values.length > 0 &&
|
|
1114
|
+
values.every(
|
|
1115
|
+
(value) =>
|
|
1116
|
+
value === null || typeof value === "string" || typeof value === "boolean" || (typeof value === "number" && Number.isFinite(value)),
|
|
1117
|
+
)
|
|
1118
|
+
)
|
|
1119
|
+
return values;
|
|
1120
|
+
};
|
|
1121
|
+
|
|
1122
|
+
const disjointObjectBranches = (left: unknown, right: unknown): boolean => {
|
|
1123
|
+
if (!isSchemaObject(left) || !isSchemaObject(right) || left.type !== "object" || right.type !== "object") return false;
|
|
1124
|
+
const leftProperties = left.properties;
|
|
1125
|
+
const rightProperties = right.properties;
|
|
1126
|
+
const leftRequired = left.required;
|
|
1127
|
+
const rightRequired = right.required;
|
|
1128
|
+
if (!isSchemaObject(leftProperties) || !isSchemaObject(rightProperties) || !Array.isArray(leftRequired) || !Array.isArray(rightRequired))
|
|
1129
|
+
return false;
|
|
1130
|
+
return leftRequired.some((name) => {
|
|
1131
|
+
if (typeof name !== "string" || !rightRequired.includes(name)) return false;
|
|
1132
|
+
const leftValues = discriminatorValues(leftProperties[name]);
|
|
1133
|
+
const rightValues = discriminatorValues(rightProperties[name]);
|
|
1134
|
+
return Boolean(leftValues && rightValues && leftValues.every((value) => !rightValues.includes(value)));
|
|
1135
|
+
});
|
|
1136
|
+
};
|
|
1137
|
+
|
|
1094
1138
|
const schemaEvolutionIssues = (previous: JsonSchema, next: JsonSchema, path: string, direction: "input" | "data"): string[] => {
|
|
1139
|
+
const schemasMatch = direction === "input" ? sameInputSchema : sameSchema;
|
|
1095
1140
|
for (const keyword of ["oneOf", "anyOf"] as const) {
|
|
1096
1141
|
const previousBranches = previous[keyword];
|
|
1097
1142
|
const nextBranches = next[keyword];
|
|
@@ -1100,9 +1145,25 @@ const schemaEvolutionIssues = (previous: JsonSchema, next: JsonSchema, path: str
|
|
|
1100
1145
|
const nextRest = { ...next };
|
|
1101
1146
|
delete previousRest[keyword];
|
|
1102
1147
|
delete nextRest[keyword];
|
|
1103
|
-
if (!
|
|
1104
|
-
|
|
1148
|
+
if (!schemasMatch(previousRest, nextRest)) return [`${path} changed`];
|
|
1149
|
+
if (direction === "input") {
|
|
1150
|
+
if (!previousBranches.every(isSchemaObject) || !nextBranches.every(isSchemaObject)) return [`${path} changed`];
|
|
1151
|
+
if (nextBranches.length < previousBranches.length) return [`${path} changed`];
|
|
1152
|
+
// anyOf is monotonic under branch additions. oneOf is not: a newly overlapping
|
|
1153
|
+
// branch rejects previously valid input, even when the branch count stays the same.
|
|
1154
|
+
if (
|
|
1155
|
+
keyword === "oneOf" &&
|
|
1156
|
+
!sameInputSchema(previousBranches, nextBranches) &&
|
|
1157
|
+
!nextBranches.every((branch, index) => nextBranches.slice(index + 1).every((other) => disjointObjectBranches(branch, other)))
|
|
1158
|
+
)
|
|
1159
|
+
return [`${path} oneOf branches are not provably disjoint`];
|
|
1160
|
+
return previousBranches.flatMap((branch, index) =>
|
|
1161
|
+
nextBranches.some((candidate) => schemaEvolutionIssues(branch, candidate, path, direction).length === 0)
|
|
1162
|
+
? []
|
|
1163
|
+
: [`${path} ${keyword}[${index}] was removed or narrowed`],
|
|
1164
|
+
);
|
|
1105
1165
|
}
|
|
1166
|
+
if (previousBranches.length !== nextBranches.length) return [`${path} changed`];
|
|
1106
1167
|
return previousBranches.flatMap((branch, index) =>
|
|
1107
1168
|
schemaEvolutionIssues(branch as JsonSchema, nextBranches[index] as JsonSchema, `${path} ${keyword}[${index}]`, direction),
|
|
1108
1169
|
);
|
|
@@ -1114,16 +1175,16 @@ const schemaEvolutionIssues = (previous: JsonSchema, next: JsonSchema, path: str
|
|
|
1114
1175
|
const nextItems = nextRest.items;
|
|
1115
1176
|
delete previousRest.items;
|
|
1116
1177
|
delete nextRest.items;
|
|
1117
|
-
const issues =
|
|
1178
|
+
const issues = schemasMatch(previousRest, nextRest) ? [] : [`${path} constraints changed`];
|
|
1118
1179
|
if (previousItems && nextItems) {
|
|
1119
1180
|
issues.push(...schemaEvolutionIssues(previousItems as JsonSchema, nextItems as JsonSchema, `${path} items`, direction));
|
|
1120
|
-
} else if (!
|
|
1181
|
+
} else if (!schemasMatch(previousItems, nextItems)) {
|
|
1121
1182
|
issues.push(`${path} items changed`);
|
|
1122
1183
|
}
|
|
1123
1184
|
return issues;
|
|
1124
1185
|
}
|
|
1125
1186
|
if (previous.type !== "object" || next.type !== "object") {
|
|
1126
|
-
return
|
|
1187
|
+
return schemasMatch(previous, next) ? [] : [`${path} changed`];
|
|
1127
1188
|
}
|
|
1128
1189
|
const issues: string[] = [];
|
|
1129
1190
|
const previousRest = { ...previous };
|
|
@@ -1132,10 +1193,15 @@ const schemaEvolutionIssues = (previous: JsonSchema, next: JsonSchema, path: str
|
|
|
1132
1193
|
delete previousRest.required;
|
|
1133
1194
|
delete nextRest.properties;
|
|
1134
1195
|
delete nextRest.required;
|
|
1135
|
-
if (!
|
|
1196
|
+
if (!schemasMatch(previousRest, nextRest)) issues.push(`${path} constraints changed`);
|
|
1136
1197
|
|
|
1137
1198
|
const previousProperties = (previous.properties ?? {}) as Record<string, JsonSchema>;
|
|
1138
1199
|
const nextProperties = (next.properties ?? {}) as Record<string, JsonSchema>;
|
|
1200
|
+
if (direction === "input" && (previous.additionalProperties !== false || previous.patternProperties !== undefined)) {
|
|
1201
|
+
for (const name of Object.keys(nextProperties)) {
|
|
1202
|
+
if (!Object.hasOwn(previousProperties, name)) issues.push(`${path}.${name} may constrain previously allowed additional input`);
|
|
1203
|
+
}
|
|
1204
|
+
}
|
|
1139
1205
|
for (const [name, schema] of Object.entries(previousProperties)) {
|
|
1140
1206
|
const nextSchema = nextProperties[name];
|
|
1141
1207
|
if (!nextSchema) issues.push(`${path}.${name} was removed`);
|
|
@@ -1302,6 +1368,15 @@ export const validateCapabilityResult = (
|
|
|
1302
1368
|
return { ok: true, data: parsed.data };
|
|
1303
1369
|
};
|
|
1304
1370
|
|
|
1371
|
+
const cancelledInvocation = (locale: string) => ({
|
|
1372
|
+
ok: false as const,
|
|
1373
|
+
error: {
|
|
1374
|
+
code: CAPABILITY_FRAMEWORK_ERROR_CODES.requestCancelled,
|
|
1375
|
+
message: capabilityMessages(locale).requestCancelled,
|
|
1376
|
+
status: 499 as const,
|
|
1377
|
+
},
|
|
1378
|
+
});
|
|
1379
|
+
|
|
1305
1380
|
export const invokeCompiledCapability = async (params: {
|
|
1306
1381
|
compiled: CompiledCapabilities;
|
|
1307
1382
|
kind: "query" | "action";
|
|
@@ -1403,6 +1478,9 @@ export const invokeCompiledCapability = async (params: {
|
|
|
1403
1478
|
)
|
|
1404
1479
|
return { ok: false, error: { code: "VALIDATION_FAILED", message: "Unsupported search scope", status: 400 } };
|
|
1405
1480
|
}
|
|
1481
|
+
// Preparation can finish after the forwarding request was cancelled. Never
|
|
1482
|
+
// cross into application code for the first time with an aborted invocation.
|
|
1483
|
+
if (context.signal.aborted) return cancelledInvocation(context.locale);
|
|
1406
1484
|
try {
|
|
1407
1485
|
const invoked = await operation.definition.run(input.data, context);
|
|
1408
1486
|
if (!invoked.ok) {
|
|
@@ -1496,6 +1574,7 @@ export const reviewCompiledCapability = async (params: {
|
|
|
1496
1574
|
},
|
|
1497
1575
|
};
|
|
1498
1576
|
}
|
|
1577
|
+
if (params.context.signal.aborted) return cancelledInvocation(params.context.locale);
|
|
1499
1578
|
try {
|
|
1500
1579
|
const reviewed = await operation.definition.review(input.data, params.context);
|
|
1501
1580
|
if (!reviewed.ok) {
|
|
@@ -67,8 +67,14 @@ export const capabilityActionReviewIssues = (review: unknown, context: Capabilit
|
|
|
67
67
|
data.links,
|
|
68
68
|
// A per-call reason never lifts the declared `sends` completeness rule.
|
|
69
69
|
data.ask === "sends" || ("ask" in context.policy && context.policy.ask === "sends"),
|
|
70
|
+
Boolean(data.content),
|
|
70
71
|
),
|
|
71
72
|
);
|
|
73
|
+
if (data.content?.kind === "message" && "ask" in context.policy && context.policy.ask === "sends") {
|
|
74
|
+
if (!data.content.from) issues.push("Full sends content requires from");
|
|
75
|
+
if (!data.content.to.length && !data.content.cc?.length && !data.content.bcc?.length)
|
|
76
|
+
issues.push("Full sends content requires a recipient");
|
|
77
|
+
}
|
|
72
78
|
return [...new Set(issues)];
|
|
73
79
|
};
|
|
74
80
|
/** A receipt's own rules; completeness links are the result envelope's top-level `links`. */
|
|
@@ -465,7 +465,8 @@ const slotText = (slot: CapabilitySlot, context: CapabilitySlotSentenceContext,
|
|
|
465
465
|
}
|
|
466
466
|
case "person": {
|
|
467
467
|
const total = slot.total ?? slot.people.length;
|
|
468
|
-
if (mode === "button")
|
|
468
|
+
if (mode === "button")
|
|
469
|
+
return total === 1 ? plain(slot.people[0]!.name ?? slot.people[0]!.address ?? "", 24, "middle") : t.people({ count: total });
|
|
469
470
|
const shown = mode === "full" ? slot.people : slot.people.slice(0, 3);
|
|
470
471
|
const list = shown.map((person) =>
|
|
471
472
|
mode === "chip" ? plain(person.name ?? person.address ?? "", 40, "middle") : capabilityPersonText(person),
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
import type { Context } from "hono";
|
|
2
|
+
import { REQUEST_IDLE_TIMEOUT_SECONDS, setRequestTimeout } from "../server/request-timeout";
|
|
3
|
+
|
|
4
|
+
type InvocationKind = "query" | "action" | "review";
|
|
5
|
+
export type CapabilityTimeoutOverrides = { queryTimeoutMs?: number; actionTimeoutMs?: number; reviewTimeoutMs?: number };
|
|
6
|
+
|
|
7
|
+
/** Discovery and recent-resource reads keep their short, independent budget. */
|
|
8
|
+
export const CAPABILITY_DISCOVERY_TIMEOUT_MS = 15_000;
|
|
9
|
+
|
|
10
|
+
export const capabilityTimeoutMs = (kind: InvocationKind, overrides: CapabilityTimeoutOverrides = {}): number => {
|
|
11
|
+
if (kind === "review") return overrides.reviewTimeoutMs ?? 15_000;
|
|
12
|
+
return kind === "query" ? (overrides.queryTimeoutMs ?? 45_000) : (overrides.actionTimeoutMs ?? 30_000);
|
|
13
|
+
};
|
|
14
|
+
|
|
15
|
+
/** Only JSON request owners use this; streaming responses own their idle lifecycle. */
|
|
16
|
+
export const withCapabilityRequestTimeout = async <T>(c: Context, kind: InvocationKind, run: () => Promise<T>): Promise<T> => {
|
|
17
|
+
setRequestTimeout(c, Math.ceil(capabilityTimeoutMs(kind) / 1000) + REQUEST_IDLE_TIMEOUT_SECONDS);
|
|
18
|
+
try {
|
|
19
|
+
return await run();
|
|
20
|
+
} finally {
|
|
21
|
+
setRequestTimeout(c, REQUEST_IDLE_TIMEOUT_SECONDS);
|
|
22
|
+
}
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
/** One monotonic budget, including preparation. Waiting reads never start after it expires. */
|
|
26
|
+
export const createCapabilityDeadline = (timeoutMs: number, caller: AbortSignal) => {
|
|
27
|
+
const controller = new AbortController();
|
|
28
|
+
const expiresAt = performance.now() + timeoutMs;
|
|
29
|
+
const expire = () => controller.abort(new DOMException("Capability deadline exceeded", "TimeoutError"));
|
|
30
|
+
const timer = setTimeout(expire, timeoutMs);
|
|
31
|
+
const signal = AbortSignal.any([caller, controller.signal]);
|
|
32
|
+
const check = () => {
|
|
33
|
+
if (performance.now() >= expiresAt) expire();
|
|
34
|
+
signal.throwIfAborted();
|
|
35
|
+
};
|
|
36
|
+
const wait = <T>(read: () => Promise<T>): Promise<T> => {
|
|
37
|
+
check();
|
|
38
|
+
return new Promise<T>((resolve, reject) => {
|
|
39
|
+
const aborted = () => reject(signal.reason);
|
|
40
|
+
signal.addEventListener("abort", aborted, { once: true });
|
|
41
|
+
// Observe both outcomes even after cancellation; a late read cannot resume dispatch.
|
|
42
|
+
Promise.resolve()
|
|
43
|
+
.then(() => {
|
|
44
|
+
check();
|
|
45
|
+
return read();
|
|
46
|
+
})
|
|
47
|
+
.then(
|
|
48
|
+
(result) => {
|
|
49
|
+
signal.removeEventListener("abort", aborted);
|
|
50
|
+
try {
|
|
51
|
+
check();
|
|
52
|
+
resolve(result);
|
|
53
|
+
} catch (error) {
|
|
54
|
+
reject(error);
|
|
55
|
+
}
|
|
56
|
+
},
|
|
57
|
+
(error) => {
|
|
58
|
+
signal.removeEventListener("abort", aborted);
|
|
59
|
+
reject(error);
|
|
60
|
+
},
|
|
61
|
+
);
|
|
62
|
+
});
|
|
63
|
+
};
|
|
64
|
+
return {
|
|
65
|
+
signal,
|
|
66
|
+
check,
|
|
67
|
+
wait,
|
|
68
|
+
remainingMs: () => Math.max(1, Math.ceil(expiresAt - performance.now())),
|
|
69
|
+
clear: () => clearTimeout(timer),
|
|
70
|
+
};
|
|
71
|
+
};
|