@hosty-sdk/app 0.37.0 → 0.39.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 +26 -0
- package/dist/agent-sessions.d.ts +73 -0
- package/dist/agent-sessions.js +1 -0
- package/dist/feedback-audio.d.ts +3 -0
- package/dist/feedback-audio.js +40 -0
- package/dist/feedback-capture.d.ts +3 -0
- package/dist/feedback-capture.js +80 -0
- package/dist/feedback-draft.d.ts +20 -0
- package/dist/feedback-draft.js +104 -0
- package/dist/feedback-react.d.ts +4 -0
- package/dist/feedback-react.js +390 -0
- package/dist/feedback-server.d.ts +31 -0
- package/dist/feedback-server.js +48 -0
- package/dist/feedback-speech.d.ts +6 -0
- package/dist/feedback-speech.js +119 -0
- package/dist/feedback-types.d.ts +122 -0
- package/dist/feedback-types.js +9 -0
- package/dist/history.d.ts +354 -0
- package/dist/history.js +1 -0
- package/dist/providers-server.d.ts +17 -1
- package/dist/providers-server.js +33 -1
- package/dist/providers.d.ts +76 -1
- package/dist/tasks-server.d.ts +11 -0
- package/dist/tasks-server.js +15 -0
- package/dist/ui-frame.js +1 -1
- package/dist/ui-react.js +2 -1
- package/dist/ui-server.js +39 -4
- package/package.json +17 -1
package/dist/tasks-server.js
CHANGED
|
@@ -55,3 +55,18 @@ export class TaskExecutorClient {
|
|
|
55
55
|
return this.call(`/executions/${encodeURIComponent(executionId)}/cancel`, "POST", { requestId }, signal);
|
|
56
56
|
}
|
|
57
57
|
}
|
|
58
|
+
/** Core-mediated child calls retain the original caller and the exact task-provider attribution. */
|
|
59
|
+
export class MediatedTaskExecutorClient {
|
|
60
|
+
providers;
|
|
61
|
+
executor;
|
|
62
|
+
dispatch;
|
|
63
|
+
constructor(providers, executor, dispatch) {
|
|
64
|
+
this.providers = providers;
|
|
65
|
+
this.executor = executor;
|
|
66
|
+
this.dispatch = dispatch;
|
|
67
|
+
}
|
|
68
|
+
options(signal) { return this.providers.taskDispatch({ dispatch: this.dispatch, executor: this.executor, operation: "options" }, signal); }
|
|
69
|
+
get(executionId, signal) { return this.providers.taskDispatch({ dispatch: this.dispatch, executor: this.executor, operation: "get", executionId }, signal); }
|
|
70
|
+
accept(executionId, input, signal) { return this.providers.taskDispatch({ dispatch: this.dispatch, executor: this.executor, operation: "accept", executionId, input }, signal); }
|
|
71
|
+
cancel(executionId, requestId, signal) { return this.providers.taskDispatch({ dispatch: this.dispatch, executor: this.executor, operation: "cancel", executionId, requestId }, signal); }
|
|
72
|
+
}
|
package/dist/ui-frame.js
CHANGED
|
@@ -137,5 +137,5 @@ export function HostyAppFrame(props) {
|
|
|
137
137
|
const insecure = typeof window !== "undefined" && window.location.protocol === "https:" && new URL(surface.origin).protocol === "http:";
|
|
138
138
|
if (insecure)
|
|
139
139
|
return _jsxs("div", { "data-hosty-sdk": true, role: "status", className: "hosty-panel-notice", children: [_jsx(SdkChromeStyles, {}), _jsxs("strong", { children: [title, " needs HTTPS to appear here"] }), _jsx("p", { children: "This app publishes an HTTP address. Configure its public HTTPS origin in Hosty." }), /^(localhost|127\.|\[::1\])/.test(new URL(surface.origin).hostname) && _jsx("p", { children: "This local address is reachable only from the Hosty computer." }), standaloneHref && _jsx("a", { href: standaloneHref, target: "_blank", rel: "noopener noreferrer", children: "Open separately" })] });
|
|
140
|
-
return _jsxs(_Fragment, { children: [review && _jsxs(_Fragment, { children: [_jsx(SdkChromeStyles, {}), _jsx("a", { "data-hosty-sdk": true, className: "hosty-panel-review", href: review, target: "_blank", rel: "noopener noreferrer", children: "Review access in Core" })] }), _jsx("iframe", { ref: ref, className: className, title: title, src: launch, sandbox: frameSandbox(surface.framePolicy), allow: surface.framePolicy.microphone ? "clipboard-write; microphone 'src'" : "clipboard-write", style: { border: 0, height: "100%", width: "100%", colorScheme: resolvedTheme }, onLoad: () => setLoaded(true) })] });
|
|
140
|
+
return _jsxs(_Fragment, { children: [review && _jsxs(_Fragment, { children: [_jsx(SdkChromeStyles, {}), _jsx("a", { "data-hosty-sdk": true, className: "hosty-panel-review", href: review, target: "_blank", rel: "noopener noreferrer", children: "Review access in Core" })] }), _jsx("iframe", { ref: ref, className: className, title: title, src: launch, sandbox: frameSandbox(surface.framePolicy), allow: surface.framePolicy.microphone ? "clipboard-write; display-capture 'src'; microphone 'src'" : "clipboard-write; display-capture 'src'", style: { border: 0, height: "100%", width: "100%", colorScheme: resolvedTheme }, onLoad: () => setLoaded(true) })] });
|
|
141
141
|
}
|
package/dist/ui-react.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
3
|
+
import { FeedbackCapture } from "./feedback-react.js";
|
|
3
4
|
import { SdkChromeStyles, SdkIcon } from "./sdk-chrome.js";
|
|
4
5
|
import { useContext, useEffect, useId, useLayoutEffect, useRef, useState, useSyncExternalStore } from "react";
|
|
5
6
|
import { bindUiClient, ui } from "./ui.js";
|
|
@@ -53,7 +54,7 @@ export function HostyUiRoot({ children, uiPath, panelLayout = "auto", active = t
|
|
|
53
54
|
if (allowed)
|
|
54
55
|
mounted.current = true;
|
|
55
56
|
const content = _jsxs("div", { className: "hosty-app-layout", children: [navigation?.renderer !== "custom" && _jsx(HostyNavigation, {}), _jsxs("div", { className: "hosty-app-content", "data-hosty-content": true, tabIndex: -1, children: [_jsx(HostyToolLauncher, {}), children] })] });
|
|
56
|
-
return _jsx(UiHostContext.Provider, { value: host, children: _jsx(UiCompositionContext.Provider, { value: clientRef.current?.composition ?? null, children: _jsxs(UiFrameLayerContext.Provider, { value: layer, children: [_jsx("style", { children: `.hosty-app-layout{display:flex;width:100%;height:100%;min-width:0;min-height:0}.hosty-app-content{flex:1;min-width:0;min-height:0;overflow:auto;outline:none}` }), _jsx("div", { hidden: !allowed, inert: !allowed, style: { display: allowed ? "contents" : "none" }, children: mounted.current && (panelLayout === "auto" && host ? _jsx(HostyPanelWorkspace, { children: content }) : content) }), !allowed && _jsxs("div", { "data-hosty-sdk": true, role: "status", className: "hosty-panel-notice", children: [_jsx(SdkChromeStyles, {}), environment?.connection === "blocked" ? _jsxs(_Fragment, { children: [_jsx("strong", { children: environment.code === "actor_mismatch" ? "Use the same Hosty account in this app and its host" : "Cannot verify this app’s host" }), _jsx("p", { children: "Check your account and access in Hosty Core, then reconnect. Your existing discussion is unchanged." }), _jsx("button", { type: "button", onClick: () => window.location.reload(), children: "Reconnect" })] }) : "Connecting to the workspace…"] }), host && _jsx(HostyPresentationLayer, { layer: layer, host: host, active: !!allowed })] }) }) });
|
|
57
|
+
return _jsx(UiHostContext.Provider, { value: host, children: _jsx(UiCompositionContext.Provider, { value: clientRef.current?.composition ?? null, children: _jsxs(UiFrameLayerContext.Provider, { value: layer, children: [_jsx("style", { children: `.hosty-app-layout{display:flex;width:100%;height:100%;min-width:0;min-height:0}.hosty-app-content{flex:1;min-width:0;min-height:0;overflow:auto;outline:none}` }), _jsx("div", { hidden: !allowed, inert: !allowed, style: { display: allowed ? "contents" : "none" }, children: mounted.current && (panelLayout === "auto" && host ? _jsx(HostyPanelWorkspace, { children: content }) : content) }), !allowed && _jsxs("div", { "data-hosty-sdk": true, role: "status", className: "hosty-panel-notice", children: [_jsx(SdkChromeStyles, {}), environment?.connection === "blocked" ? _jsxs(_Fragment, { children: [_jsx("strong", { children: environment.code === "actor_mismatch" ? "Use the same Hosty account in this app and its host" : "Cannot verify this app’s host" }), _jsx("p", { children: "Check your account and access in Hosty Core, then reconnect. Your existing discussion is unchanged." }), _jsx("button", { type: "button", onClick: () => window.location.reload(), children: "Reconnect" })] }) : "Connecting to the workspace…"] }), _jsx(FeedbackCapture, { active: active && !!allowed, uiPath: uiPath }), host && _jsx(HostyPresentationLayer, { layer: layer, host: host, active: !!allowed })] }) }) });
|
|
57
58
|
}
|
|
58
59
|
const empty = { bootstrap: null, catalog: [], tools: [], selected: null, expanded: false, width: 360, error: null };
|
|
59
60
|
const noop = () => () => { };
|
package/dist/ui-server.js
CHANGED
|
@@ -1,3 +1,7 @@
|
|
|
1
|
+
import { FeedbackServerClient } from "@hosty-sdk/app/feedback/server";
|
|
2
|
+
import { FeedbackError } from "@hosty-sdk/app/feedback";
|
|
3
|
+
import { ProviderClient } from "@hosty-sdk/app/providers/server";
|
|
4
|
+
import { ProviderError } from "@hosty-sdk/app/providers";
|
|
1
5
|
import { UiError } from "@hosty-sdk/app/ui/types";
|
|
2
6
|
/** Server transport for generic UI metadata. Credentials never appear in returned metadata. */
|
|
3
7
|
export class UiServerClient {
|
|
@@ -63,7 +67,38 @@ export async function createHostyUiResponse(request, context, options = {}) {
|
|
|
63
67
|
const route = url.pathname.startsWith(prefix + "/") ? url.pathname.slice(prefix.length + 1) : "";
|
|
64
68
|
const client = new UiServerClient(options);
|
|
65
69
|
let result;
|
|
66
|
-
if (request.method === "GET"
|
|
70
|
+
if (/^feedback\/[a-f0-9]{64}\/review-targets$/.test(route) && request.method === "GET") {
|
|
71
|
+
result = await new FeedbackServerClient(options).reviewTargets(route.split("/")[1], context.token, request.signal);
|
|
72
|
+
}
|
|
73
|
+
else if (route === "feedback/speech" && request.method === "GET") {
|
|
74
|
+
const speech = new ProviderClient(options), permissions = await speech.permissions(request.signal);
|
|
75
|
+
const granted = permissions.granted.includes("providers.speech-to-text");
|
|
76
|
+
result = { granted, providers: granted ? await speech.list("speech-to-text", request.signal) : [] };
|
|
77
|
+
}
|
|
78
|
+
else if (route === "feedback/speech" && request.method === "POST") {
|
|
79
|
+
if (!sameOrigin(request) || request.headers.get("content-type")?.split(";", 1)[0]?.trim() !== "application/json")
|
|
80
|
+
throw new UiError("origin_denied", "Dictate from this application page.", 403);
|
|
81
|
+
const speech = new ProviderClient(options), roster = await speech.list("speech-to-text", request.signal);
|
|
82
|
+
const input = JSON.parse(await boundedBody(request, 6 * 1024 * 1024));
|
|
83
|
+
if (!input || typeof input.appId !== "string" || typeof input.key !== "string" || typeof input.audio !== "string")
|
|
84
|
+
throw new UiError("speech_invalid", "Choose a speech provider and a recording.", 400);
|
|
85
|
+
if (!roster.some(p => p.appId === input.appId && p.key === input.key && p.available))
|
|
86
|
+
throw new UiError("speech_unavailable", "The selected speech provider is unavailable.", 409);
|
|
87
|
+
const bytes = Uint8Array.from(Buffer.from(input.audio, "base64"));
|
|
88
|
+
if (bytes.length < 44 || bytes.length > 120 * 16000 * 2 + 4096 || String.fromCharCode(...bytes.subarray(0, 4)) !== "RIFF" || String.fromCharCode(...bytes.subarray(8, 12)) !== "WAVE")
|
|
89
|
+
throw new UiError("speech_invalid", "Use a bounded WAV recording.", 400);
|
|
90
|
+
result = await speech.transcribe(input, new Blob([bytes], { type: "audio/wav" }), { signal: request.signal });
|
|
91
|
+
}
|
|
92
|
+
else if (route === "feedback" && request.method === "GET") {
|
|
93
|
+
result = await new FeedbackServerClient(options).capabilities(context.token, request.signal);
|
|
94
|
+
}
|
|
95
|
+
else if (route === "feedback" && request.method === "POST") {
|
|
96
|
+
if (!sameOrigin(request) || request.headers.get("content-type")?.split(";", 1)[0]?.trim() !== "application/json")
|
|
97
|
+
throw new UiError("origin_denied", "Submit feedback from this application page.", 403);
|
|
98
|
+
const body = await boundedBody(request, 12 * 1024 * 1024);
|
|
99
|
+
result = await new FeedbackServerClient(options).submit(JSON.parse(body), context.token, request.signal);
|
|
100
|
+
}
|
|
101
|
+
else if (request.method === "GET" && route === "bootstrap") {
|
|
67
102
|
if ([...url.searchParams.keys()].some(k => k !== "parentOrigin") || url.searchParams.getAll("parentOrigin").length > 1)
|
|
68
103
|
throw new UiError("ui_request_invalid", "Invalid bootstrap request.", 400);
|
|
69
104
|
result = await client.bootstrap({ parentOrigin: url.searchParams.get("parentOrigin") ?? undefined }, context.token, request.signal);
|
|
@@ -93,12 +128,12 @@ export async function createHostyUiResponse(request, context, options = {}) {
|
|
|
93
128
|
return Response.json(result, { headers: responseHeaders });
|
|
94
129
|
}
|
|
95
130
|
catch (error) {
|
|
96
|
-
const known = error instanceof UiError;
|
|
131
|
+
const known = error instanceof UiError || error instanceof FeedbackError || error instanceof ProviderError;
|
|
97
132
|
return Response.json({ code: known ? error.code : error instanceof SyntaxError ? "ui_request_invalid" : "ui_unavailable",
|
|
98
133
|
message: known ? error.message : "Application panels are unavailable. Retry opening the same target." }, { status: known ? error.status : error instanceof SyntaxError ? 400 : 503, headers: responseHeaders });
|
|
99
134
|
}
|
|
100
135
|
}
|
|
101
|
-
async function boundedBody(request) {
|
|
136
|
+
async function boundedBody(request, limit = 16_384) {
|
|
102
137
|
const reader = request.body?.getReader();
|
|
103
138
|
if (!reader)
|
|
104
139
|
return "";
|
|
@@ -110,7 +145,7 @@ async function boundedBody(request) {
|
|
|
110
145
|
if (done)
|
|
111
146
|
break;
|
|
112
147
|
length += value.byteLength;
|
|
113
|
-
if (length >
|
|
148
|
+
if (length > limit) {
|
|
114
149
|
await reader.cancel();
|
|
115
150
|
throw new UiError("ui_request_invalid", "Panel request is too large.", 413);
|
|
116
151
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@hosty-sdk/app",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.39.0",
|
|
4
4
|
"description": "Hosty app auth: session state machine, recovery bridge, Core revalidation, and the embedder responder.",
|
|
5
5
|
"license": "AGPL-3.0-only",
|
|
6
6
|
"type": "module",
|
|
@@ -140,6 +140,22 @@
|
|
|
140
140
|
"./tasks/server": {
|
|
141
141
|
"types": "./dist/tasks-server.d.ts",
|
|
142
142
|
"default": "./dist/tasks-server.js"
|
|
143
|
+
},
|
|
144
|
+
"./feedback": {
|
|
145
|
+
"types": "./dist/feedback-types.d.ts",
|
|
146
|
+
"default": "./dist/feedback-types.js"
|
|
147
|
+
},
|
|
148
|
+
"./feedback/server": {
|
|
149
|
+
"types": "./dist/feedback-server.d.ts",
|
|
150
|
+
"default": "./dist/feedback-server.js"
|
|
151
|
+
},
|
|
152
|
+
"./agent-sessions": {
|
|
153
|
+
"types": "./dist/agent-sessions.d.ts",
|
|
154
|
+
"default": "./dist/agent-sessions.js"
|
|
155
|
+
},
|
|
156
|
+
"./history": {
|
|
157
|
+
"types": "./dist/history.d.ts",
|
|
158
|
+
"default": "./dist/history.js"
|
|
143
159
|
}
|
|
144
160
|
},
|
|
145
161
|
"scripts": {
|