@hosty-sdk/app 0.28.0 → 0.29.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.
@@ -53,10 +53,14 @@ export function CoreEnvironmentPanel({ request, storageKey, workspace, workspace
53
53
  const item = selected.find(e => e.id === saved.id);
54
54
  const operation = item?.operations.find(o => o.id.replaceAll("-", "") === String(saved.body.requestId).replaceAll("-", ""));
55
55
  if (operation && ["succeeded", "failed"].includes(operation.state)) {
56
- savePending(null);
56
+ const superseded = operation !== item?.operations.at(-1);
57
+ if (operation.state === "succeeded" || saved.kind !== "finish" || superseded)
58
+ savePending(null);
57
59
  setError(operation.state === "failed" ? item?.error ?? "Sandbox operation failed." : "");
58
60
  if (item?.state === "finished")
59
61
  setNotice("Sandbox deleted. Workspace source is preserved.");
62
+ else if (superseded)
63
+ setNotice("A newer operation replaced the previous request. Review the current sandbox status.");
60
64
  }
61
65
  }
62
66
  const results = await Promise.all(selected.filter(e => e.state === "running").map(async (e) => {
@@ -117,7 +121,8 @@ export function CoreEnvironmentPanel({ request, storageKey, workspace, workspace
117
121
  try {
118
122
  savePending(value);
119
123
  const result = await request(value.path, value.body);
120
- savePending(null);
124
+ if (value.kind !== "finish" || result.state === "finished")
125
+ savePending(null);
121
126
  if (result.error)
122
127
  throw new Error(result.error);
123
128
  if (result.state === "finished")
@@ -153,7 +158,7 @@ export function CoreEnvironmentPanel({ request, storageKey, workspace, workspace
153
158
  }
154
159
  }
155
160
  const disabled = busy || pending !== null;
156
- return _jsxs("section", { className: "space-y-4 rounded-lg border bg-card p-4", "aria-label": "Sandbox environments", children: [_jsxs("header", { className: "flex flex-wrap items-center justify-between gap-3", children: [_jsxs("div", { children: [_jsx("h3", { className: "font-semibold", children: "Sandbox environments" }), _jsx("p", { className: "text-sm text-muted-foreground", children: "Separate Core instances with their own apps, users and temporary data." })] }), _jsxs("div", { className: "flex gap-2", children: [_jsx("button", { className: button, onClick: () => { setError(""); void refresh(); }, children: "Refresh sandboxes" }), workspace && _jsx("button", { className: button, disabled: disabled || !workspace.worktrees.some(t => t.state === "active"), onClick: () => setCreating(!creating), children: "Create sandbox" })] })] }), creating && workspace && _jsx(CreateEnvironment, { workspace: workspace, disabled: disabled, onCreate: create, onCancel: () => setCreating(false) }), notice && _jsx("p", { role: "status", className: "text-sm text-muted-foreground", children: notice }), (error || readError) && _jsx("p", { role: "alert", className: "text-sm text-destructive", children: error || readError }), pending && _jsxs("div", { className: "flex items-center gap-3 text-sm", children: [_jsx("span", { children: busy ? `Sandbox ${pending.kind} is in progress…` : "The request outcome is not confirmed." }), _jsx("button", { className: button, disabled: busy, onClick: () => void run(pending), children: "Retry same request" })] }), loading ? _jsx("p", { role: "status", className: "text-sm text-muted-foreground", children: "Loading sandboxes\u2026" }) : !environments.length && !error && !readError && _jsx("p", { className: "text-sm text-muted-foreground", children: "No active sandbox environments." }), environments.map(item => _jsxs("article", { className: "space-y-3 rounded-md border p-3", "aria-label": `Sandbox ${item.label}`, children: [_jsxs("header", { className: "flex flex-wrap items-start justify-between gap-3", children: [_jsxs("div", { children: [_jsxs("h4", { className: "font-medium", children: [item.label, " ", _jsx("span", { className: "ml-2 rounded border px-2 py-0.5 text-xs font-normal", children: item.state })] }), _jsxs("p", { className: "mt-1 text-xs text-muted-foreground", children: [item.mode === "source" ? "Core built from workspace source" : item.mode === "release" ? "Installed Core release" : "Current Core build", item.context?.workspaceId && workspaceHref && _jsxs(_Fragment, { children: [" \u00B7 ", _jsx("a", { className: "underline", href: workspaceHref(item.context.workspaceId), children: "Open workspace" })] })] })] }), _jsxs("div", { className: "flex flex-wrap gap-2", children: [item.state === "running" && link(item.previewUrl) && _jsx("a", { className: button, href: item.previewUrl + "/login", target: "_blank", rel: "noreferrer", children: "Open Core" }), _jsx("button", { className: button, disabled: disabled || ["building", "starting", "stopping", "finishing", "cleanup-failed"].includes(item.state), onClick: () => void command(item, item.state === "running" ? "stop" : "start"), children: item.state === "running" ? "Stop" : "Start" }), _jsx("button", { className: button + " text-destructive", disabled: disabled || item.state === "finishing", onClick: () => setRemoving(item.id), children: item.state === "cleanup-failed" ? "Retry deletion" : "Delete sandbox" })] })] }), item.error && _jsx("p", { role: "alert", className: "text-sm text-destructive", children: item.error }), item.state === "running" && _jsxs(_Fragment, { children: [_jsx(EnvironmentApplications, { value: apps[item.id] }), apps[item.id] && ["setup-required", "sign-in-required"].includes(apps[item.id].state) && _jsx(SetupAccount, { request: request, id: item.id, disabled: disabled, onDone: () => refresh() })] }), removing === item.id && _jsxs("div", { role: "alertdialog", "aria-label": `Delete ${item.label}`, className: "space-y-3 rounded-md border border-destructive p-3", children: [_jsx("p", { className: "text-sm", children: "Permanently delete this sandbox, all its apps, test users, data and caches? Workspace source and shared folders are preserved." }), _jsxs("div", { className: "flex gap-2", children: [_jsx("button", { className: button + " text-destructive", disabled: disabled, onClick: () => { setRemoving(null); void command(item, "finish"); }, children: "Delete permanently" }), _jsx("button", { className: button, onClick: () => setRemoving(null), children: "Cancel" })] })] })] }, item.id))] });
161
+ return _jsxs("section", { className: "space-y-4 rounded-lg border bg-card p-4", "aria-label": "Sandbox environments", children: [_jsxs("header", { className: "flex flex-wrap items-center justify-between gap-3", children: [_jsxs("div", { children: [_jsx("h3", { className: "font-semibold", children: "Sandbox environments" }), _jsx("p", { className: "text-sm text-muted-foreground", children: "Separate Core instances with their own apps, users and temporary data." })] }), _jsxs("div", { className: "flex gap-2", children: [_jsx("button", { className: button, onClick: () => { setError(""); void refresh(); }, children: "Refresh sandboxes" }), workspace && _jsx("button", { className: button, disabled: disabled || !workspace.worktrees.some(t => t.state === "active"), onClick: () => setCreating(!creating), children: "Create sandbox" })] })] }), creating && workspace && _jsx(CreateEnvironment, { workspace: workspace, disabled: disabled, onCreate: create, onCancel: () => setCreating(false) }), notice && _jsx("p", { role: "status", className: "text-sm text-muted-foreground", children: notice }), (error || readError) && _jsx("p", { role: "alert", className: "text-sm text-destructive", children: error || readError }), pending && _jsxs("div", { className: "flex items-center gap-3 text-sm", children: [_jsx("span", { children: busy ? `Sandbox ${pending.kind} is in progress…` : "The request outcome is not confirmed." }), _jsx("button", { className: button, disabled: busy, onClick: () => void run(pending), children: "Retry same request" })] }), loading ? _jsx("p", { role: "status", className: "text-sm text-muted-foreground", children: "Loading sandboxes\u2026" }) : !environments.length && !error && !readError && _jsx("p", { className: "text-sm text-muted-foreground", children: "No active sandbox environments." }), environments.map(item => _jsxs("article", { className: "space-y-3 rounded-md border p-3", "aria-label": `Sandbox ${item.label}`, children: [_jsxs("header", { className: "flex flex-wrap items-start justify-between gap-3", children: [_jsxs("div", { children: [_jsxs("h4", { className: "font-medium", children: [item.label, " ", _jsx("span", { className: "ml-2 rounded border px-2 py-0.5 text-xs font-normal", children: item.state })] }), _jsxs("p", { className: "mt-1 text-xs text-muted-foreground", children: [item.mode === "source" ? "Core built from workspace source" : item.mode === "release" ? "Installed Core release" : "Current Core build", item.context?.workspaceId && workspaceHref && _jsxs(_Fragment, { children: [" \u00B7 ", _jsx("a", { className: "underline", href: workspaceHref(item.context.workspaceId), children: "Open workspace" })] })] })] }), _jsxs("div", { className: "flex flex-wrap gap-2", children: [item.state === "running" && link(item.previewUrl) && _jsx("a", { className: button, href: item.previewUrl + "/login", target: "_blank", rel: "noreferrer", children: "Open Core" }), _jsx("button", { className: button, disabled: disabled || ["building", "starting", "stopping", "finishing", "cleanup-failed"].includes(item.state), onClick: () => void command(item, item.state === "running" ? "stop" : "start"), children: item.state === "running" ? "Stop" : "Start" }), _jsx("button", { className: button + " text-destructive", disabled: disabled || item.state === "finishing", onClick: () => setRemoving(item.id), children: item.state === "cleanup-failed" ? "Retry deletion" : "Delete sandbox" })] })] }), item.limits && _jsxs("p", { className: "text-xs text-muted-foreground", children: ["Reserved: ", (item.limits.memoryBytes / 1073741824).toFixed(1), " GiB container memory \u00B7 ", item.limits.cpus, " CPUs \u00B7 ", item.limits.pids, " PIDs \u00B7 ", (item.limits.diskBytes / 1073741824).toFixed(1), " GiB soft disk limit. Released after deletion."] }), item.usage && _jsx("p", { className: "text-xs text-muted-foreground", children: item.usage.state === "unknown" ? "Resource usage unavailable; the reservation remains active." : `Observed: ${(item.usage.containerMemoryBytes / 1048576).toFixed(0)} MiB containers · ${(item.usage.coreMemoryBytes / 1048576).toFixed(0)} MiB Core process · ${(item.usage.ownedDiskBytes / 1073741824).toFixed(2)} GiB owned data.` }), item.error && _jsx("p", { role: "alert", className: "text-sm text-destructive", children: item.error }), item.state === "running" && _jsxs(_Fragment, { children: [_jsx(EnvironmentApplications, { value: apps[item.id] }), apps[item.id] && ["setup-required", "sign-in-required"].includes(apps[item.id].state) && _jsx(SetupAccount, { request: request, id: item.id, disabled: disabled, onDone: () => refresh() })] }), removing === item.id && _jsxs("div", { role: "alertdialog", "aria-label": `Delete ${item.label}`, className: "space-y-3 rounded-md border border-destructive p-3", children: [_jsx("p", { className: "text-sm", children: "Permanently delete this sandbox, all its apps, test users, data and caches? Workspace source and shared folders are preserved." }), _jsxs("div", { className: "flex gap-2", children: [_jsx("button", { className: button + " text-destructive", disabled: disabled, onClick: () => { setRemoving(null); void command(item, "finish"); }, children: "Delete permanently" }), _jsx("button", { className: button, onClick: () => setRemoving(null), children: "Cancel" })] })] })] }, item.id))] });
157
162
  }
158
163
  function EnvironmentApplications({ value }) {
159
164
  if (!value)
@@ -150,6 +150,23 @@ export interface CoreEnvironment {
150
150
  };
151
151
  error?: string;
152
152
  operations: SandboxOperation[];
153
+ limits?: {
154
+ memoryBytes: number;
155
+ cpus: number;
156
+ pids: number;
157
+ diskBytes: number;
158
+ applicationMemoryBytes: number;
159
+ applicationCpus: number;
160
+ applicationPids: number;
161
+ };
162
+ usage?: {
163
+ ownedDiskBytes: number;
164
+ containerMemoryBytes: number;
165
+ coreMemoryBytes: number;
166
+ state: string;
167
+ observedAt: string;
168
+ error?: string;
169
+ };
153
170
  }
154
171
  /** Full child Core transport. Requires core.environments permissions and live administrator authority.
155
172
  * Setup uses ordinary password authentication. Parent credentials are never forwarded to the child. */
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@hosty-sdk/app",
3
- "version": "0.28.0",
3
+ "version": "0.29.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",