@fungi.computer/oyster 0.1.0 → 0.3.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 +376 -72
- package/dist/agent-avatars.d.ts +103 -0
- package/dist/agent-avatars.js +66 -0
- package/dist/agents.d.ts +15 -4
- package/dist/agents.js +43 -4
- package/dist/app-builds.d.ts +81 -8
- package/dist/app-builds.js +210 -60
- package/dist/app-storage.d.ts +3 -3
- package/dist/approval-capability.d.ts +5 -0
- package/dist/approval-capability.js +46 -0
- package/dist/approval-home.d.ts +51 -0
- package/dist/approval-home.js +226 -0
- package/dist/apps/catalog.d.ts +335 -0
- package/dist/apps/catalog.js +46 -0
- package/dist/apps/gate.d.ts +26 -12
- package/dist/apps/gate.js +48 -22
- package/dist/apps/index.d.ts +3 -1
- package/dist/apps/index.js +3 -1
- package/dist/apps/installation.d.ts +147 -66
- package/dist/apps/installation.js +193 -86
- package/dist/apps/permissions.d.ts +247 -0
- package/dist/apps/permissions.js +255 -0
- package/dist/boot-capability-route.d.ts +15 -0
- package/dist/boot-capability-route.js +196 -0
- package/dist/boot-capability.d.ts +103 -0
- package/dist/boot-capability.js +141 -0
- package/dist/boot-construction.d.ts +12 -0
- package/dist/boot-construction.js +175 -0
- package/dist/boot-gate.d.ts +267 -0
- package/dist/boot-gate.js +236 -0
- package/dist/boot-host-script.d.ts +2 -0
- package/dist/boot-host-script.js +1 -0
- package/dist/boot-mount.d.ts +21 -0
- package/dist/boot-mount.js +65 -0
- package/dist/browser/boot.d.ts +73 -0
- package/dist/browser/boot.js +502 -0
- package/dist/browser/browser.d.ts +104 -0
- package/dist/browser/browser.js +374 -0
- package/dist/browser/capability-source.d.ts +76 -0
- package/dist/browser/capability-source.js +338 -0
- package/dist/browser/cdp-client.d.ts +56 -0
- package/dist/browser/cdp-client.js +184 -0
- package/dist/browser/errors.d.ts +79 -0
- package/dist/browser/errors.js +32 -0
- package/dist/browser/index.d.ts +16 -0
- package/dist/browser/index.js +14 -0
- package/dist/browser/lease-registry.d.ts +61 -0
- package/dist/browser/lease-registry.js +101 -0
- package/dist/browser/operations.d.ts +225 -0
- package/dist/browser/operations.js +137 -0
- package/dist/browser/page.d.ts +25 -0
- package/dist/browser/page.js +172 -0
- package/dist/browser/quick-reads.d.ts +29 -0
- package/dist/browser/quick-reads.js +160 -0
- package/dist/browser/sessions.d.ts +52 -0
- package/dist/browser/sessions.js +118 -0
- package/dist/browser/url-policy.d.ts +18 -0
- package/dist/browser/url-policy.js +58 -0
- package/dist/build/app-imports.d.ts +12 -0
- package/dist/build/app-imports.js +105 -0
- package/dist/build/build-policy.d.ts +5 -3
- package/dist/build/build-policy.js +5 -3
- package/dist/build/candidate-builder.d.ts +1 -1
- package/dist/build/candidate-cli.mjs +8428 -337
- package/dist/build/local-build.d.ts +2 -0
- package/dist/build/local-build.js +28 -6
- package/dist/build/snapshot-package-store.d.ts +1 -1
- package/dist/build/snapshot-package-store.js +7 -2
- package/dist/build/workspace-build.d.ts +92 -5
- package/dist/build/workspace-build.js +93 -19
- package/dist/build-egress.js +4 -4
- package/dist/build.d.ts +1 -1
- package/dist/build.js +1 -1
- package/dist/catalog-frame.js +1 -1
- package/dist/cloudflare.d.ts +13 -3
- package/dist/cloudflare.js +106 -8
- package/dist/frame-binding-wire-script.js +1 -1
- package/dist/frame-binding-wire.d.ts +1 -1
- package/dist/frame-binding-wire.js +5 -3
- package/dist/git-auth.d.ts +0 -2
- package/dist/git-auth.js +0 -2
- package/dist/git-journal.js +4 -4
- package/dist/git.d.ts +0 -1
- package/dist/git.js +0 -2
- package/dist/host.d.ts +39 -13
- package/dist/host.js +268 -142
- package/dist/index.d.ts +26 -15
- package/dist/index.js +40 -16
- package/dist/install-release-cli.d.ts +2 -0
- package/dist/install-release-cli.js +46 -0
- package/dist/installation-client.d.ts +43 -0
- package/dist/installation-client.js +178 -0
- package/dist/installation-execution.d.ts +11 -0
- package/dist/installation-execution.js +184 -0
- package/dist/installation-operations.d.ts +1437 -0
- package/dist/installation-operations.js +664 -0
- package/dist/installation-store-generation.d.ts +3 -0
- package/dist/installation-store-generation.js +16 -0
- package/dist/instance-id.d.ts +10 -0
- package/dist/instance-id.js +10 -0
- package/dist/instance-refusal.d.ts +11 -0
- package/dist/instance-refusal.js +21 -0
- package/dist/internal/delivery.js +4 -2
- package/dist/internal/host-gate.js +7 -1
- package/dist/internal/registry-installations.js +4 -4
- package/dist/keys.d.ts +5 -1
- package/dist/keys.js +10 -2
- package/dist/machine-link/authority.d.ts +89 -0
- package/dist/machine-link/authority.js +37 -0
- package/dist/machine-link/client.d.ts +66 -0
- package/dist/machine-link/client.js +94 -0
- package/dist/machine-link/delivery.d.ts +18 -0
- package/dist/machine-link/delivery.js +55 -0
- package/dist/machine-link/enrollment.d.ts +111 -0
- package/dist/machine-link/enrollment.js +286 -0
- package/dist/machine-link/fences.d.ts +37 -0
- package/dist/machine-link/fences.js +166 -0
- package/dist/machine-link/home.d.ts +20 -0
- package/dist/machine-link/home.js +24 -0
- package/dist/machine-link/host.d.ts +43 -0
- package/dist/machine-link/host.js +189 -0
- package/dist/machine-link/index.d.ts +15 -0
- package/dist/machine-link/index.js +15 -0
- package/dist/machine-link/link.d.ts +48 -0
- package/dist/machine-link/link.js +223 -0
- package/dist/machine-link/presence.d.ts +10 -0
- package/dist/machine-link/presence.js +25 -0
- package/dist/machine-link/workers.d.ts +6 -0
- package/dist/machine-link/workers.js +6 -0
- package/dist/mcp-owner.d.ts +2 -2
- package/dist/mcp-owner.js +41 -33
- package/dist/mcp.d.ts +15 -3
- package/dist/mcp.js +146 -25
- package/dist/pack.d.ts +14 -3
- package/dist/pack.js +44 -19
- package/dist/page/app-access.d.ts +7 -0
- package/dist/page/app-access.js +75 -0
- package/dist/page/boot-host.js +57 -0
- package/dist/page/capabilities.d.ts +3 -3
- package/dist/page/capabilities.js +16 -12
- package/dist/page/computer-handles.d.ts +3 -3
- package/dist/page/computer-target.d.ts +3 -0
- package/dist/page/computer-target.js +9 -0
- package/dist/page/mount.d.ts +23 -7
- package/dist/page/mount.js +152 -16
- package/dist/page-assets.d.ts +22 -0
- package/dist/page-assets.js +28 -0
- package/dist/page-script.d.ts +3 -0
- package/dist/page-script.js +5 -4
- package/dist/release.d.ts +13 -6
- package/dist/release.js +10 -7
- package/dist/root-data.d.ts +16 -6
- package/dist/root-data.js +2 -2
- package/dist/source/index.d.ts +1 -1
- package/dist/source/team-source.d.ts +20 -8
- package/dist/source/team-source.js +39 -10
- package/dist/source/template-seed.d.ts +2 -2
- package/dist/standalone/agent-mail.d.ts +24 -0
- package/dist/standalone/agent-mail.js +62 -0
- package/dist/standalone/agent-store.d.ts +9 -0
- package/dist/standalone/agent-store.js +30 -0
- package/dist/standalone/agents.d.ts +78 -12
- package/dist/standalone/agents.js +470 -59
- package/dist/standalone/app-builds-binding.d.ts +20 -2
- package/dist/standalone/app-builds-binding.js +151 -27
- package/dist/standalone/app-set.d.ts +5 -2
- package/dist/standalone/app-set.js +174 -143
- package/dist/standalone/browser.d.ts +11 -0
- package/dist/standalone/browser.js +72 -0
- package/dist/standalone/builds.d.ts +13 -4
- package/dist/standalone/builds.js +28 -44
- package/dist/standalone/computer-egress.d.ts +2 -0
- package/dist/standalone/computer-egress.js +13 -4
- package/dist/standalone/computer-fleet.d.ts +43 -0
- package/dist/standalone/computer-fleet.js +200 -0
- package/dist/standalone/computer.d.ts +44 -9
- package/dist/standalone/computer.js +132 -25
- package/dist/standalone/install-apps-cli.js +43 -6
- package/dist/standalone/instance.d.ts +2 -2
- package/dist/standalone/instance.js +8 -11
- package/dist/standalone/machine-link.d.ts +354 -0
- package/dist/standalone/machine-link.js +368 -0
- package/dist/standalone/media.d.ts +2 -2
- package/dist/standalone/media.js +19 -16
- package/dist/standalone/preview.d.ts +2 -2
- package/dist/standalone/preview.js +39 -12
- package/dist/standalone/registry.d.ts +149 -9
- package/dist/standalone/registry.js +299 -8
- package/dist/standalone/worker.d.ts +19 -5
- package/dist/standalone/worker.js +90 -54
- package/dist/standalone/workflow-binding.d.ts +28 -32
- package/dist/standalone/workflow-binding.js +20 -20
- package/dist/standalone/workflow-control.d.ts +4 -0
- package/dist/standalone/workflow-control.js +40 -0
- package/dist/standalone/workflow-events.d.ts +2 -2
- package/dist/standalone/workflow-events.js +9 -10
- package/dist/standalone/workflow-git-capture.js +4 -3
- package/dist/standalone/workflow-host.d.ts +177 -152
- package/dist/standalone/workflow-host.js +137 -30
- package/dist/standalone/workflow-node-capture.js +11 -4
- package/dist/standalone/workflow-schedules.d.ts +5 -2
- package/dist/standalone/workflow-schedules.js +9 -10
- package/dist/standalone/workflow-trigger-host.d.ts +88 -6
- package/dist/standalone/workflow-trigger-host.js +2 -2
- package/dist/standalone/workflow-webhooks.d.ts +22 -0
- package/dist/standalone/workflow-webhooks.js +39 -0
- package/dist/standalone/workflows.d.ts +203 -50
- package/dist/standalone/workflows.js +198 -39
- package/dist/storage-frame.d.ts +6 -6
- package/dist/testing/capabilities.js +14 -3
- package/dist/testing/installation.js +26 -2
- package/dist/testing/worker.js +1 -1
- package/dist/testing/workers-runtime.d.ts +8 -2
- package/dist/testing/workers-runtime.js +20 -2
- package/dist/webhook-abuse.d.ts +25 -0
- package/dist/webhook-abuse.js +54 -0
- package/dist/webhook-custody.d.ts +144 -0
- package/dist/webhook-custody.js +222 -0
- package/dist/webhook-ingress.d.ts +36 -0
- package/dist/webhook-ingress.js +146 -0
- package/dist/webhooks.d.ts +111 -0
- package/dist/webhooks.js +189 -0
- package/dist/workers-git.d.ts +12 -17
- package/dist/workers-git.js +60 -55
- package/dist/workers.d.ts +224 -13
- package/dist/workers.js +351 -51
- package/dist/workflow-agent-authority.d.ts +8 -0
- package/dist/workflow-agent-authority.js +42 -0
- package/dist/workflows.d.ts +66 -0
- package/dist/workflows.js +97 -0
- package/docs/api/README.md +63 -37
- package/docs/api/agents.md +17 -0
- package/docs/api/app-storage.md +1 -1
- package/docs/api/apps.md +1394 -543
- package/docs/api/browser/boot.md +132 -0
- package/docs/api/browser.md +1440 -0
- package/docs/api/build-contract.md +38 -25
- package/docs/api/build.md +263 -84
- package/docs/api/cloudflare.md +93 -7
- package/docs/api/git.md +3 -13
- package/docs/api/host.md +944 -118
- package/docs/api/index.md +320 -18
- package/docs/api/keys.md +24 -1
- package/docs/api/mcp.md +59 -12
- package/docs/api/modules.md +33 -40
- package/docs/api/pack.md +57 -8
- package/docs/api/page/mount.md +92 -4
- package/docs/api/workers-git.md +103 -24
- package/docs/api/workers.md +1581 -244
- package/docs/site/agent-quickstart.mdx +28 -14
- package/examples/standalone/worker.ts +3 -1
- package/examples/standalone/wrangler.jsonc +2 -0
- package/package.json +73 -69
- package/dist/installation-consent.d.ts +0 -79
- package/dist/installation-consent.js +0 -82
- package/dist/shell/app-directory.d.ts +0 -542
- package/dist/shell/app-directory.js +0 -199
- package/dist/shell/browser-actions.d.ts +0 -7
- package/dist/shell/browser-actions.js +0 -56
- package/dist/shell/command-host.d.ts +0 -112
- package/dist/shell/command-host.js +0 -502
- package/dist/shell/commands.d.ts +0 -25
- package/dist/shell/commands.js +0 -78
- package/dist/shell/index.d.ts +0 -8
- package/dist/shell/index.js +0 -8
- package/dist/shell/keys.d.ts +0 -41
- package/dist/shell/keys.js +0 -134
- package/dist/shell/shell-module.d.ts +0 -55
- package/dist/shell/shell-module.js +0 -86
- package/dist/shell/window-manager.d.ts +0 -124
- package/dist/shell/window-manager.js +0 -711
- package/dist/shell/workbench-contract.d.ts +0 -68
- package/dist/shell/workbench-directory.d.ts +0 -332
- package/dist/shell/workbench-directory.js +0 -35
- package/dist/shell/workbench-host-types.d.ts +0 -96
- package/dist/shell/workbench-host-types.js +0 -1
- package/dist/shell/workbench-host.d.ts +0 -34
- package/dist/shell/workbench-host.js +0 -347
- package/dist/shell/workbench-model.d.ts +0 -192
- package/dist/shell/workbench-model.js +0 -53
- package/dist/shell/workbench.d.ts +0 -4
- package/dist/shell/workbench.js +0 -2
- package/docs/api/shell/app-directory.md +0 -599
- package/docs/api/shell/command-host.md +0 -224
- package/docs/api/shell/commands.md +0 -197
- package/docs/api/shell/keys.md +0 -183
- package/docs/api/shell/shell-module.md +0 -204
- package/docs/api/shell/window-manager.md +0 -267
- package/docs/api/shell/workbench-directory.md +0 -119
- package/docs/api/shell/workbench.md +0 -205
- package/docs/api/shell.md +0 -1915
- /package/dist/{shell/workbench-contract.js → page/boot-host.d.ts} +0 -0
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
import { grantRequestFor, appPermissionPageSchema, } from "../apps/permissions.js";
|
|
2
|
+
/**
|
|
3
|
+
* Keep standalone consent in the Oyster host: the picked shell draws the
|
|
4
|
+
* prompt, while Oyster bounds the request and commits through its owner CAS.
|
|
5
|
+
*/
|
|
6
|
+
export function createShellAppAccess(directory) {
|
|
7
|
+
return async (target, input, dialog) => {
|
|
8
|
+
const { surfaceId, computerId } = target;
|
|
9
|
+
const current = await directory();
|
|
10
|
+
const app = current.apps.find((row) => row.surfaceId === surfaceId);
|
|
11
|
+
const installationId = app?.installationId ?? null;
|
|
12
|
+
const customize = () => dialog.customize(installationId);
|
|
13
|
+
if (installationId === null)
|
|
14
|
+
return customize();
|
|
15
|
+
const path = `/api/installations/${encodeURIComponent(installationId)}/grants`;
|
|
16
|
+
const response = await fetch(path, {
|
|
17
|
+
headers: { Accept: "application/json" },
|
|
18
|
+
credentials: "same-origin",
|
|
19
|
+
cache: "no-store",
|
|
20
|
+
redirect: "error",
|
|
21
|
+
});
|
|
22
|
+
if (!response.ok)
|
|
23
|
+
return "declined";
|
|
24
|
+
const view = appPermissionPageSchema.parse(await response.json());
|
|
25
|
+
const { permissions } = view;
|
|
26
|
+
const decision = grantRequestFor(permissions.authority, {
|
|
27
|
+
declaration: {
|
|
28
|
+
capabilities: permissions.declaredOperations.capabilities.map((row) => row.capability),
|
|
29
|
+
},
|
|
30
|
+
declaredOperations: permissions.declaredOperations,
|
|
31
|
+
}, input, { computerId });
|
|
32
|
+
if (decision.kind === "covered")
|
|
33
|
+
return "granted";
|
|
34
|
+
if (decision.kind === "refused") {
|
|
35
|
+
if (decision.code !== "window_scope_unavailable")
|
|
36
|
+
return "declined";
|
|
37
|
+
const outcome = await dialog.ask({
|
|
38
|
+
appName: view.appName,
|
|
39
|
+
agents: input.agents ?? [],
|
|
40
|
+
computers: input.computers ?? [],
|
|
41
|
+
refusal: {
|
|
42
|
+
code: decision.code,
|
|
43
|
+
message: "Allow for this window is unavailable on this Oyster. No access was changed. Use Customize to review Computer permissions explicitly.",
|
|
44
|
+
},
|
|
45
|
+
allow: () => Promise.resolve(false),
|
|
46
|
+
});
|
|
47
|
+
return outcome === "customize" ? customize() : outcome;
|
|
48
|
+
}
|
|
49
|
+
if (decision.kind === "customize")
|
|
50
|
+
return customize();
|
|
51
|
+
const outcome = await dialog.ask({
|
|
52
|
+
appName: view.appName,
|
|
53
|
+
agents: input.agents ?? [],
|
|
54
|
+
computers: input.computers ?? [],
|
|
55
|
+
allow: async () => {
|
|
56
|
+
const saved = await fetch(path, {
|
|
57
|
+
method: "PUT",
|
|
58
|
+
headers: { "Content-Type": "application/json" },
|
|
59
|
+
credentials: "same-origin",
|
|
60
|
+
cache: "no-store",
|
|
61
|
+
redirect: "error",
|
|
62
|
+
body: JSON.stringify({
|
|
63
|
+
storeGeneration: view.storeGeneration,
|
|
64
|
+
authorityVersion: permissions.authority.authorityVersion,
|
|
65
|
+
artifactDigest: view.artifactDigest,
|
|
66
|
+
command: decision.command,
|
|
67
|
+
}),
|
|
68
|
+
});
|
|
69
|
+
return saved.ok;
|
|
70
|
+
},
|
|
71
|
+
});
|
|
72
|
+
// False saves settle declined; rejected saves leave the shell open for retry.
|
|
73
|
+
return outcome === "customize" ? customize() : outcome;
|
|
74
|
+
};
|
|
75
|
+
}
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
import { connectAppHost } from "@fungi.computer/agar/host";
|
|
2
|
+
import { z } from "zod";
|
|
3
|
+
import { bootUiSchema } from "../boot-gate.js";
|
|
4
|
+
/**
|
|
5
|
+
* Trusted, zero-grant parent for a candidate entry. App readiness messages
|
|
6
|
+
* establish the ordinary Agar connection, never the boot-gate verdict.
|
|
7
|
+
*/
|
|
8
|
+
async function bootHost() {
|
|
9
|
+
const element = document.getElementById("boot-config");
|
|
10
|
+
if (element === null)
|
|
11
|
+
throw new Error("Host boot configuration unavailable");
|
|
12
|
+
const config = bootUiSchema
|
|
13
|
+
.pick({ identity: true })
|
|
14
|
+
.extend({ entry: z.url() })
|
|
15
|
+
.parse(JSON.parse(element.textContent));
|
|
16
|
+
const frame = document.getElementById("candidate");
|
|
17
|
+
if (!(frame instanceof HTMLIFrameElement) || frame.contentWindow === null)
|
|
18
|
+
throw new Error("Host candidate frame unavailable");
|
|
19
|
+
const denied = () => Promise.reject(new Error("No grants during host boot"));
|
|
20
|
+
const connection = connectAppHost({
|
|
21
|
+
appWindow: frame.contentWindow,
|
|
22
|
+
expectedAppOrigin: "null",
|
|
23
|
+
hostWindow: window,
|
|
24
|
+
launch: {
|
|
25
|
+
identity: config.identity,
|
|
26
|
+
capabilities: [],
|
|
27
|
+
context: {
|
|
28
|
+
version: 1,
|
|
29
|
+
value: {
|
|
30
|
+
computerId: null,
|
|
31
|
+
focus: { editing: false, surface: null },
|
|
32
|
+
openRequest: null,
|
|
33
|
+
visibility: "visible",
|
|
34
|
+
},
|
|
35
|
+
},
|
|
36
|
+
lifecycle: { version: 1, value: "connected" },
|
|
37
|
+
reservedChords: { version: 1, value: [] },
|
|
38
|
+
theme: { version: 1, value: "light" },
|
|
39
|
+
},
|
|
40
|
+
handlers: {
|
|
41
|
+
invokeReservedChord: denied,
|
|
42
|
+
onDisconnect: () => Promise.resolve(),
|
|
43
|
+
openNavigation: denied,
|
|
44
|
+
registerCommands: () => Promise.resolve({
|
|
45
|
+
update: () => undefined,
|
|
46
|
+
dispose: () => undefined,
|
|
47
|
+
}),
|
|
48
|
+
reportFocus: () => Promise.resolve(),
|
|
49
|
+
reportAgentSessionSelection: () => Promise.resolve(),
|
|
50
|
+
showToast: () => Promise.resolve(),
|
|
51
|
+
},
|
|
52
|
+
});
|
|
53
|
+
// Install the host connection listener before the candidate can announce ready.
|
|
54
|
+
frame.src = config.entry;
|
|
55
|
+
await connection;
|
|
56
|
+
}
|
|
57
|
+
await bootHost();
|
|
@@ -5,7 +5,7 @@ import type { CapabilityDescriptor } from "../capability-descriptor.js";
|
|
|
5
5
|
/** Agar's opaque host handle, shared by the page's exported factory contracts. */
|
|
6
6
|
export type HostCapabilityHandle = AppHostLaunch["capabilities"][number]["handle"];
|
|
7
7
|
/** Bind a page-local JSON caller; cancellation never enters serialized input. */
|
|
8
|
-
export declare function capabilityJsonCaller(descriptor: CapabilityDescriptor, installationId: string): (method: string, raw?: unknown, options?: {
|
|
8
|
+
export declare function capabilityJsonCaller(descriptor: CapabilityDescriptor, installationId: string, computerTarget?: string): (method: string, raw?: unknown, options?: {
|
|
9
9
|
readonly signal?: AbortSignal;
|
|
10
10
|
}) => Promise<import("zod").JSONType | undefined>;
|
|
11
11
|
/** Local JSON calls carry cancellation separately from their serialized input. */
|
|
@@ -13,11 +13,11 @@ export type CapabilityJsonCaller = ReturnType<typeof capabilityJsonCaller>;
|
|
|
13
13
|
/** Local factories may open only transports present in their descriptor. */
|
|
14
14
|
export type CapabilityTransportOpener = (method: string, input?: Schema.Json) => WebSocket;
|
|
15
15
|
/** Build the App's RPC handle from its admitted descriptor and installation. */
|
|
16
|
-
export declare function capabilityHandle(descriptor: CapabilityDescriptor, installationId: string): HostCapabilityHandle;
|
|
16
|
+
export declare function capabilityHandle(descriptor: CapabilityDescriptor, installationId: string, computerTarget?: string): HostCapabilityHandle;
|
|
17
17
|
/** A supported contract may adapt local JSON calls into its App-facing handle. */
|
|
18
18
|
export type CapabilityRegistration = Readonly<{
|
|
19
19
|
contract: CapabilityContract<unknown>;
|
|
20
20
|
createHandle?: (call: CapabilityJsonCaller, transport: CapabilityTransportOpener) => HostCapabilityHandle | Promise<HostCapabilityHandle>;
|
|
21
21
|
}>;
|
|
22
22
|
/** Create a registered handle, using the descriptor's ordinary RPC handle by default. */
|
|
23
|
-
export declare function registeredCapabilityHandle(registration: CapabilityRegistration, descriptor: CapabilityDescriptor, installationId: string): Promise<HostCapabilityHandle>;
|
|
23
|
+
export declare function registeredCapabilityHandle(registration: CapabilityRegistration, descriptor: CapabilityDescriptor, installationId: string, computerTarget?: string): Promise<HostCapabilityHandle>;
|
|
@@ -3,6 +3,7 @@ import { compile, parse, pathToRegexp } from "path-to-regexp";
|
|
|
3
3
|
import * as Schema from "effect/Schema";
|
|
4
4
|
import { AppError } from "@fungi.computer/agar";
|
|
5
5
|
import { parseJsonValue } from "@fungi.computer/whistle/wire";
|
|
6
|
+
import { computerTargetUrl } from "./computer-target.js";
|
|
6
7
|
const queryInput = Schema.Record(Schema.String, Schema.Union([Schema.String, Schema.Number, Schema.Boolean]));
|
|
7
8
|
const capabilityInputSchema = Schema.Union([Schema.Json, Schema.Undefined]);
|
|
8
9
|
function resolveRoute(path, input) {
|
|
@@ -163,7 +164,7 @@ class TransportHandle extends RpcTarget {
|
|
|
163
164
|
return Promise.resolve();
|
|
164
165
|
}
|
|
165
166
|
}
|
|
166
|
-
function capabilityRequest(descriptor, installationId, route, raw) {
|
|
167
|
+
function capabilityRequest(descriptor, installationId, route, raw, computerTarget) {
|
|
167
168
|
const resolved = resolveRoute(route.path, raw);
|
|
168
169
|
const input = resolved.input;
|
|
169
170
|
const headers = new Headers({ "X-Oyster-Installation": installationId });
|
|
@@ -184,9 +185,12 @@ function capabilityRequest(descriptor, installationId, route, raw) {
|
|
|
184
185
|
if (route.kind === "json" && route.method !== "GET") {
|
|
185
186
|
headers.set("Content-Type", "application/json");
|
|
186
187
|
init.body = JSON.stringify(input);
|
|
187
|
-
return { url, init };
|
|
188
|
+
return { url: computerTargetUrl(url, computerTarget), init };
|
|
188
189
|
}
|
|
189
|
-
return {
|
|
190
|
+
return {
|
|
191
|
+
url: computerTargetUrl(`${url}${query(input)}`, computerTarget),
|
|
192
|
+
init,
|
|
193
|
+
};
|
|
190
194
|
}
|
|
191
195
|
async function jsonReply(url, init) {
|
|
192
196
|
const response = await fetch(url, init);
|
|
@@ -196,7 +200,7 @@ async function jsonReply(url, init) {
|
|
|
196
200
|
return parseJsonValue(await response.json());
|
|
197
201
|
}
|
|
198
202
|
/** Bind a page-local JSON caller; cancellation never enters serialized input. */
|
|
199
|
-
export function capabilityJsonCaller(descriptor, installationId) {
|
|
203
|
+
export function capabilityJsonCaller(descriptor, installationId, computerTarget) {
|
|
200
204
|
return async (method, raw, options) => {
|
|
201
205
|
const input = Schema.decodeUnknownSync(capabilityInputSchema)(raw);
|
|
202
206
|
const route = descriptor.methods[method];
|
|
@@ -205,12 +209,12 @@ export function capabilityJsonCaller(descriptor, installationId) {
|
|
|
205
209
|
code: "INVALID_ARGUMENT",
|
|
206
210
|
retryable: false,
|
|
207
211
|
});
|
|
208
|
-
const { url, init } = capabilityRequest(descriptor, installationId, route, input);
|
|
212
|
+
const { url, init } = capabilityRequest(descriptor, installationId, route, input, computerTarget);
|
|
209
213
|
return jsonReply(url, { ...init, signal: options?.signal });
|
|
210
214
|
};
|
|
211
215
|
}
|
|
212
216
|
/** Open a browser transport using the page's admitted installation. */
|
|
213
|
-
function capabilityTransportOpener(descriptor, installationId) {
|
|
217
|
+
function capabilityTransportOpener(descriptor, installationId, computerTarget) {
|
|
214
218
|
return (method, raw) => {
|
|
215
219
|
const route = descriptor.methods[method];
|
|
216
220
|
if (route?.kind !== "transport" ||
|
|
@@ -219,7 +223,7 @@ function capabilityTransportOpener(descriptor, installationId) {
|
|
|
219
223
|
code: "INVALID_ARGUMENT",
|
|
220
224
|
retryable: false,
|
|
221
225
|
});
|
|
222
|
-
const { url } = capabilityRequest(descriptor, installationId, route, Schema.decodeUnknownSync(capabilityInputSchema)(raw));
|
|
226
|
+
const { url } = capabilityRequest(descriptor, installationId, route, Schema.decodeUnknownSync(capabilityInputSchema)(raw), computerTarget);
|
|
223
227
|
const target = new URL(url, "https://oyster.invalid");
|
|
224
228
|
// Browser WebSockets cannot send the installation header used by Fetch.
|
|
225
229
|
target.searchParams.set("oysterInstallation", installationId);
|
|
@@ -227,7 +231,7 @@ function capabilityTransportOpener(descriptor, installationId) {
|
|
|
227
231
|
};
|
|
228
232
|
}
|
|
229
233
|
/** Build the App's RPC handle from its admitted descriptor and installation. */
|
|
230
|
-
export function capabilityHandle(descriptor, installationId) {
|
|
234
|
+
export function capabilityHandle(descriptor, installationId, computerTarget) {
|
|
231
235
|
// Cap'n Web exposes prototype methods, never an instance's own properties.
|
|
232
236
|
// Only input crosses RPC; AbortSignal options belong to the local caller.
|
|
233
237
|
class CapabilityHandle extends RpcTarget {
|
|
@@ -236,7 +240,7 @@ export function capabilityHandle(descriptor, installationId) {
|
|
|
236
240
|
name,
|
|
237
241
|
async (raw) => {
|
|
238
242
|
const input = Schema.decodeUnknownSync(capabilityInputSchema)(raw);
|
|
239
|
-
const { url, init } = capabilityRequest(descriptor, installationId, route, input);
|
|
243
|
+
const { url, init } = capabilityRequest(descriptor, installationId, route, input, computerTarget);
|
|
240
244
|
if (route.kind === "read")
|
|
241
245
|
return new ReadHandle(url, init);
|
|
242
246
|
if (route.kind === "write")
|
|
@@ -249,8 +253,8 @@ export function capabilityHandle(descriptor, installationId) {
|
|
|
249
253
|
return new CapabilityHandle();
|
|
250
254
|
}
|
|
251
255
|
/** Create a registered handle, using the descriptor's ordinary RPC handle by default. */
|
|
252
|
-
export async function registeredCapabilityHandle(registration, descriptor, installationId) {
|
|
256
|
+
export async function registeredCapabilityHandle(registration, descriptor, installationId, computerTarget) {
|
|
253
257
|
if (registration.createHandle === undefined)
|
|
254
|
-
return capabilityHandle(descriptor, installationId);
|
|
255
|
-
return await registration.createHandle(capabilityJsonCaller(descriptor, installationId), capabilityTransportOpener(descriptor, installationId));
|
|
258
|
+
return capabilityHandle(descriptor, installationId, computerTarget);
|
|
259
|
+
return await registration.createHandle(capabilityJsonCaller(descriptor, installationId, computerTarget), capabilityTransportOpener(descriptor, installationId, computerTarget));
|
|
256
260
|
}
|
|
@@ -46,7 +46,7 @@ export declare class ComputerProcessesHandle extends RpcTarget implements Handle
|
|
|
46
46
|
readonly startedAt: number;
|
|
47
47
|
readonly state: "running" | "exited" | "uncertain";
|
|
48
48
|
readonly exitCode: number | null;
|
|
49
|
-
readonly sync: "
|
|
49
|
+
readonly sync: "unknown" | "pending" | "synced";
|
|
50
50
|
readonly endedAt: number | null;
|
|
51
51
|
}>;
|
|
52
52
|
list(): Promise<{
|
|
@@ -58,7 +58,7 @@ export declare class ComputerProcessesHandle extends RpcTarget implements Handle
|
|
|
58
58
|
readonly startedAt: number;
|
|
59
59
|
readonly state: "running" | "exited" | "uncertain";
|
|
60
60
|
readonly exitCode: number | null;
|
|
61
|
-
readonly sync: "
|
|
61
|
+
readonly sync: "unknown" | "pending" | "synced";
|
|
62
62
|
readonly endedAt: number | null;
|
|
63
63
|
}[];
|
|
64
64
|
}>;
|
|
@@ -71,7 +71,7 @@ export declare class ComputerProcessesHandle extends RpcTarget implements Handle
|
|
|
71
71
|
readonly startedAt: number;
|
|
72
72
|
readonly state: "running" | "exited" | "uncertain";
|
|
73
73
|
readonly exitCode: number | null;
|
|
74
|
-
readonly sync: "
|
|
74
|
+
readonly sync: "unknown" | "pending" | "synced";
|
|
75
75
|
readonly endedAt: number | null;
|
|
76
76
|
};
|
|
77
77
|
readonly signalled: boolean;
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
/** Serialize a window's routing hint. The server must admit it through the live
|
|
2
|
+
* owner and fleet before constructing that request's workspace. */
|
|
3
|
+
export function computerTargetUrl(path, computerId) {
|
|
4
|
+
if (computerId === undefined)
|
|
5
|
+
return path;
|
|
6
|
+
const url = new URL(path, "https://oyster.invalid");
|
|
7
|
+
url.searchParams.set("computerId", computerId);
|
|
8
|
+
return url.pathname + url.search;
|
|
9
|
+
}
|
package/dist/page/mount.d.ts
CHANGED
|
@@ -1,7 +1,21 @@
|
|
|
1
1
|
import { type AppOpenInput, type AppRoleChoice } from "@fungi.computer/agar/apps";
|
|
2
|
+
import type { WhistleRuntime } from "@fungi.computer/whistle";
|
|
2
3
|
import * as Schema from "effect/Schema";
|
|
4
|
+
import { type CapabilityDirectory, type AgentSessionSelection } from "@fungi.computer/agar";
|
|
3
5
|
import { type AppHost, type AppHostLaunch, type AppHostHandlers } from "@fungi.computer/agar/host";
|
|
4
6
|
import type { WhistleJsonValue } from "@fungi.computer/whistle";
|
|
7
|
+
/** One page-wide selected Session, using only its reporting App's admitted view. */
|
|
8
|
+
export declare class OysterAgentBrowserHost {
|
|
9
|
+
#private;
|
|
10
|
+
/** End the selected connection with the page's own lifetime. */
|
|
11
|
+
constructor(signal: AbortSignal);
|
|
12
|
+
/** A departing or deselecting mount cannot close a different active mount. */
|
|
13
|
+
release(frame: HTMLIFrameElement): void;
|
|
14
|
+
/** Release the page's current selection; later reports cannot reconnect it. */
|
|
15
|
+
dispose(): void;
|
|
16
|
+
/** Bind the reporting mount's current command view to its explicitly selected Session. */
|
|
17
|
+
report(frame: HTMLIFrameElement, selection: AgentSessionSelection | null, capabilities: CapabilityDirectory, whistle: Pick<WhistleRuntime, "snapshot" | "execute">): Promise<void>;
|
|
18
|
+
}
|
|
5
19
|
/** Current server-admitted configuration for one installed App page. */
|
|
6
20
|
export declare const oysterAppConfigSchema: Schema.Struct<{
|
|
7
21
|
readonly workbench: Schema.Boolean;
|
|
@@ -30,9 +44,7 @@ export declare const oysterAppConfigSchema: Schema.Struct<{
|
|
|
30
44
|
keybind?: string | undefined;
|
|
31
45
|
}>) | undefined;
|
|
32
46
|
help?: Readonly<{
|
|
33
|
-
order
|
|
34
|
-
/** Role and App opens, routed by the embedding shell; the App never names a surface. */
|
|
35
|
-
? /** Role and App opens, routed by the embedding shell; the App never names a surface. */: number | undefined;
|
|
47
|
+
order?: number | undefined;
|
|
36
48
|
}> | undefined;
|
|
37
49
|
menu?: Readonly<{
|
|
38
50
|
order?: number | undefined;
|
|
@@ -64,9 +76,7 @@ export declare const oysterAppConfigSchema: Schema.Struct<{
|
|
|
64
76
|
keybind?: string | undefined;
|
|
65
77
|
}>) | undefined;
|
|
66
78
|
help?: Readonly<{
|
|
67
|
-
order
|
|
68
|
-
/** Role and App opens, routed by the embedding shell; the App never names a surface. */
|
|
69
|
-
? /** Role and App opens, routed by the embedding shell; the App never names a surface. */: number | undefined;
|
|
79
|
+
order?: number | undefined;
|
|
70
80
|
}> | undefined;
|
|
71
81
|
menu?: Readonly<{
|
|
72
82
|
order?: number | undefined;
|
|
@@ -145,6 +155,12 @@ export type OysterAppConfig = typeof oysterAppConfigSchema.Type;
|
|
|
145
155
|
/** One isolated App connection, with the slots this host admitted for it. */
|
|
146
156
|
export type OysterAppConnectionOptions = Readonly<{
|
|
147
157
|
config: OysterAppConfig;
|
|
158
|
+
/** The page-wide selected Session owner shared by all child mounts. */
|
|
159
|
+
agentBrowser: OysterAgentBrowserHost;
|
|
160
|
+
/** The existing command owner's live, mount-scoped Agent view. */
|
|
161
|
+
agentCommands: Pick<WhistleRuntime, "snapshot" | "execute">;
|
|
162
|
+
/** An independently admitted per-window target; absent on fixed/default hosts. */
|
|
163
|
+
computerTarget?: string | undefined;
|
|
148
164
|
frame: HTMLIFrameElement;
|
|
149
165
|
signal: AbortSignal;
|
|
150
166
|
handlers: AppHostHandlers;
|
|
@@ -160,4 +176,4 @@ export declare function connectOysterApp(options: OysterAppConnectionOptions): P
|
|
|
160
176
|
export { oysterStorageHandle } from "./storage.js";
|
|
161
177
|
export { appSandbox } from "../app-sandbox.js";
|
|
162
178
|
/** Resolve only the slots admitted for this installation by its current host. */
|
|
163
|
-
export declare function oysterAppCapabilities(options: Pick<OysterAppConnectionOptions, "config" | "signal" | "appOpen">): Promise<AppHostLaunch["capabilities"]>;
|
|
179
|
+
export declare function oysterAppCapabilities(options: Pick<OysterAppConnectionOptions, "config" | "signal" | "appOpen" | "computerTarget">): Promise<AppHostLaunch["capabilities"]>;
|
package/dist/page/mount.js
CHANGED
|
@@ -1,19 +1,23 @@
|
|
|
1
1
|
import { appOpenInputSchema, appsCall, appsOpen, } from "@fungi.computer/agar/apps";
|
|
2
2
|
import { appApiMethodNameSchema } from "@fungi.computer/puffball/app";
|
|
3
3
|
import { appIdSchema } from "@fungi.computer/puffball/effect";
|
|
4
|
-
import { createBrowserActionHandler } from "
|
|
5
|
-
import { createAppSurfaceCommandHost } from "
|
|
4
|
+
import { createBrowserActionHandler } from "@fungi.computer/nacre";
|
|
5
|
+
import { createAppSurfaceCommandHost } from "@fungi.computer/nacre/command-host";
|
|
6
6
|
// App page: mounts one installed App's sandboxed frame and gives it the
|
|
7
7
|
// capabilities this Oyster host provides. Bundled into `shellScript`.
|
|
8
|
-
import { RpcTarget } from "@fungi.computer/capnweb";
|
|
8
|
+
import { RpcTarget, RpcStub } from "@fungi.computer/capnweb";
|
|
9
|
+
import { EventSource } from "eventsource";
|
|
10
|
+
import { connectBrowser, UiBrowserError, } from "@fungi.computer/knapsack/ui/browser";
|
|
9
11
|
import { createPageLifetime } from "./lifetime.js";
|
|
12
|
+
import { computerTargetUrl } from "./computer-target.js";
|
|
10
13
|
import * as Schema from "effect/Schema";
|
|
11
|
-
import { AppError } from "@fungi.computer/agar";
|
|
14
|
+
import { AppError, createCapabilityRegistry, } from "@fungi.computer/agar";
|
|
12
15
|
import { appStorage } from "@fungi.computer/agar/storage";
|
|
16
|
+
import { approvals } from "@fungi.computer/agar/approvals";
|
|
13
17
|
import { oysterStorageHandle } from "./storage.js";
|
|
14
18
|
import { appBackend, appMethods } from "@fungi.computer/agar/backend";
|
|
15
19
|
import { computerList, computerPorts, computerProcesses, computerReadFile, computerTerminal, computerWriteFile, } from "@fungi.computer/agar/computer";
|
|
16
|
-
import { agentList, agentManage, agentSessionRead, agentSessionWrite, } from "@fungi.computer/agar/agents";
|
|
20
|
+
import { agentList, agentManage, agentSessionRead, agentSessionWrite, openAppAgent, } from "@fungi.computer/agar/agents";
|
|
17
21
|
import { mediaGeneration } from "@fungi.computer/agar/media";
|
|
18
22
|
import { MediaGeneration, mediaGenerationDescriptionSchema, } from "@fungi.computer/agar/media-json";
|
|
19
23
|
import { registeredCapabilityHandle, } from "./capabilities.js";
|
|
@@ -23,6 +27,105 @@ import { parseJsonValue } from "@fungi.computer/whistle/wire";
|
|
|
23
27
|
import { appDeclaredCommandSchema } from "@fungi.computer/puffball/app";
|
|
24
28
|
import { TerminalCapability } from "./terminal.js";
|
|
25
29
|
import { ComputerPortsHandle, ComputerProcessesHandle, } from "./computer-handles.js";
|
|
30
|
+
/** One page-wide selected Session, using only its reporting App's admitted view. */
|
|
31
|
+
export class OysterAgentBrowserHost {
|
|
32
|
+
#active;
|
|
33
|
+
#disposed = false;
|
|
34
|
+
/** End the selected connection with the page's own lifetime. */
|
|
35
|
+
constructor(signal) {
|
|
36
|
+
if (signal.aborted)
|
|
37
|
+
this.dispose();
|
|
38
|
+
else
|
|
39
|
+
signal.addEventListener("abort", () => {
|
|
40
|
+
this.dispose();
|
|
41
|
+
}, { once: true });
|
|
42
|
+
}
|
|
43
|
+
/** A departing or deselecting mount cannot close a different active mount. */
|
|
44
|
+
release(frame) {
|
|
45
|
+
if (this.#active?.frame !== frame)
|
|
46
|
+
return;
|
|
47
|
+
const previous = this.#active;
|
|
48
|
+
this.#active = undefined;
|
|
49
|
+
previous.close();
|
|
50
|
+
}
|
|
51
|
+
/** Release the page's current selection; later reports cannot reconnect it. */
|
|
52
|
+
dispose() {
|
|
53
|
+
this.#disposed = true;
|
|
54
|
+
if (this.#active !== undefined)
|
|
55
|
+
this.release(this.#active.frame);
|
|
56
|
+
}
|
|
57
|
+
/** Bind the reporting mount's current command view to its explicitly selected Session. */
|
|
58
|
+
async report(frame, selection, capabilities, whistle) {
|
|
59
|
+
if (this.#disposed)
|
|
60
|
+
return;
|
|
61
|
+
if (selection === null) {
|
|
62
|
+
this.release(frame);
|
|
63
|
+
return;
|
|
64
|
+
}
|
|
65
|
+
if (this.#active !== undefined)
|
|
66
|
+
this.release(this.#active.frame);
|
|
67
|
+
// Read-only Apps may observe Sessions, but cannot claim or settle UI work.
|
|
68
|
+
if (!capabilities.has(agentSessionWrite) && !capabilities.has(agentManage))
|
|
69
|
+
return;
|
|
70
|
+
let transport;
|
|
71
|
+
let connection;
|
|
72
|
+
const failure = () => {
|
|
73
|
+
console.warn("Oyster Agent browser connection failed");
|
|
74
|
+
};
|
|
75
|
+
const active = {
|
|
76
|
+
frame,
|
|
77
|
+
close: () => {
|
|
78
|
+
connection?.close();
|
|
79
|
+
if (transport !== undefined)
|
|
80
|
+
void transport.close().catch(failure);
|
|
81
|
+
},
|
|
82
|
+
};
|
|
83
|
+
this.#active = active;
|
|
84
|
+
try {
|
|
85
|
+
transport = await openAppAgent(capabilities, {
|
|
86
|
+
agentId: selection.agentId,
|
|
87
|
+
});
|
|
88
|
+
if (this.#active !== active) {
|
|
89
|
+
await transport.close();
|
|
90
|
+
return;
|
|
91
|
+
}
|
|
92
|
+
const admitted = transport;
|
|
93
|
+
connection = connectBrowser({
|
|
94
|
+
baseUrl: `${admitted.baseUrl}/browser/sessions/${encodeURIComponent(selection.sessionId)}`,
|
|
95
|
+
whistle,
|
|
96
|
+
transport: {
|
|
97
|
+
fetch: admitted.fetch,
|
|
98
|
+
openEvents: (url) => new EventSource(url, { fetch: admitted.fetch }),
|
|
99
|
+
},
|
|
100
|
+
});
|
|
101
|
+
void connection.closed.catch((cause) => {
|
|
102
|
+
if (this.#active === active)
|
|
103
|
+
this.release(frame);
|
|
104
|
+
console.warn("Oyster Agent browser connection failed", cause instanceof UiBrowserError
|
|
105
|
+
? cause.message
|
|
106
|
+
: "Transport unavailable");
|
|
107
|
+
});
|
|
108
|
+
}
|
|
109
|
+
catch (cause) {
|
|
110
|
+
if (this.#active === active)
|
|
111
|
+
this.release(frame);
|
|
112
|
+
throw cause;
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
function agentDirectory(capabilities) {
|
|
117
|
+
const directory = createCapabilityRegistry();
|
|
118
|
+
const read = capabilities.find(({ id, version }) => id === agentSessionRead.id && version === agentSessionRead.version);
|
|
119
|
+
const write = capabilities.find(({ id, version }) => id === agentSessionWrite.id && version === agentSessionWrite.version);
|
|
120
|
+
const manage = capabilities.find(({ id, version }) => id === agentManage.id && version === agentManage.version);
|
|
121
|
+
if (read !== undefined)
|
|
122
|
+
directory.provide(agentSessionRead, agentSessionRead.schema.parse(new RpcStub(read.handle)));
|
|
123
|
+
if (write !== undefined)
|
|
124
|
+
directory.provide(agentSessionWrite, agentSessionWrite.schema.parse(new RpcStub(write.handle)));
|
|
125
|
+
if (manage !== undefined)
|
|
126
|
+
directory.provide(agentManage, agentManage.schema.parse(new RpcStub(manage.handle)));
|
|
127
|
+
return directory;
|
|
128
|
+
}
|
|
26
129
|
/** Current server-admitted configuration for one installed App page. */
|
|
27
130
|
export const oysterAppConfigSchema = Schema.Struct({
|
|
28
131
|
workbench: Schema.Boolean,
|
|
@@ -59,15 +162,17 @@ async function answer(response) {
|
|
|
59
162
|
}
|
|
60
163
|
/** The App's typed methods, reached through this host's method route. */
|
|
61
164
|
class Methods extends RpcTarget {
|
|
165
|
+
computerTarget;
|
|
62
166
|
#url;
|
|
63
|
-
constructor(url) {
|
|
167
|
+
constructor(url, computerTarget) {
|
|
64
168
|
super();
|
|
169
|
+
this.computerTarget = computerTarget;
|
|
65
170
|
this.#url = url;
|
|
66
171
|
}
|
|
67
172
|
async call(method, input) {
|
|
68
173
|
const name = Schema.decodeUnknownSync(Schema.String.check(Schema.isPattern(/^[A-Za-z][A-Za-z0-9_.]{0,79}$/u)))(method);
|
|
69
174
|
const body = Schema.decodeUnknownSync(Schema.String)(input);
|
|
70
|
-
return answer(await fetch(`${this.#url}${name}`, {
|
|
175
|
+
return answer(await fetch(computerTargetUrl(`${this.#url}${name}`, this.computerTarget), {
|
|
71
176
|
method: "POST",
|
|
72
177
|
headers: { "Content-Type": "application/json" },
|
|
73
178
|
body,
|
|
@@ -81,10 +186,12 @@ class Methods extends RpcTarget {
|
|
|
81
186
|
class Calls extends RpcTarget {
|
|
82
187
|
base;
|
|
83
188
|
signal;
|
|
84
|
-
|
|
189
|
+
computerTarget;
|
|
190
|
+
constructor(base, signal, computerTarget) {
|
|
85
191
|
super();
|
|
86
192
|
this.base = base;
|
|
87
193
|
this.signal = signal;
|
|
194
|
+
this.computerTarget = computerTarget;
|
|
88
195
|
}
|
|
89
196
|
async call(appId, method, input) {
|
|
90
197
|
const target = Schema.decodeUnknownSync(appIdSchema)(appId);
|
|
@@ -96,7 +203,7 @@ class Calls extends RpcTarget {
|
|
|
96
203
|
retryable: false,
|
|
97
204
|
});
|
|
98
205
|
Schema.decodeUnknownSync(Schema.fromJsonString(Schema.Json))(body);
|
|
99
|
-
return answer(await fetch(`${this.base}${encodeURIComponent(target)}/${encodeURIComponent(name)}`, {
|
|
206
|
+
return answer(await fetch(computerTargetUrl(`${this.base}${encodeURIComponent(target)}/${encodeURIComponent(name)}`, this.computerTarget), {
|
|
100
207
|
method: "POST",
|
|
101
208
|
headers: { "Content-Type": "application/json" },
|
|
102
209
|
body,
|
|
@@ -205,11 +312,14 @@ export async function mountOysterPage() {
|
|
|
205
312
|
// The App's commands are still admitted against its Release declaration.
|
|
206
313
|
const commands = createAppSurfaceCommandHost();
|
|
207
314
|
const mount = commands.mount(config.installationId, config.commands);
|
|
315
|
+
const agentBrowser = new OysterAgentBrowserHost(lifetime.signal);
|
|
208
316
|
lifetime.own(() => {
|
|
209
317
|
commands.dispose();
|
|
210
318
|
});
|
|
211
319
|
return connectOysterApp({
|
|
212
320
|
config,
|
|
321
|
+
agentBrowser,
|
|
322
|
+
agentCommands: mount.agentCommands,
|
|
213
323
|
frame,
|
|
214
324
|
signal: lifetime.signal,
|
|
215
325
|
reservedChords: [],
|
|
@@ -234,6 +344,11 @@ export async function connectOysterApp(options) {
|
|
|
234
344
|
if (frame.contentWindow === null)
|
|
235
345
|
throw new Error("App frame is unavailable");
|
|
236
346
|
const capabilities = await oysterAppCapabilities(options);
|
|
347
|
+
const agents = agentDirectory(capabilities);
|
|
348
|
+
const releaseBrowser = () => {
|
|
349
|
+
options.agentBrowser.release(frame);
|
|
350
|
+
};
|
|
351
|
+
options.signal.addEventListener("abort", releaseBrowser, { once: true });
|
|
237
352
|
const host = await connectAppHost({
|
|
238
353
|
appWindow: frame.contentWindow,
|
|
239
354
|
expectedAppOrigin: "null",
|
|
@@ -261,6 +376,14 @@ export async function connectOysterApp(options) {
|
|
|
261
376
|
},
|
|
262
377
|
handlers: {
|
|
263
378
|
...options.handlers,
|
|
379
|
+
reportAgentSessionSelection: async (selection) => {
|
|
380
|
+
await options.agentBrowser.report(frame, selection, agents, options.agentCommands);
|
|
381
|
+
await options.handlers.reportAgentSessionSelection(selection);
|
|
382
|
+
},
|
|
383
|
+
onDisconnect: async () => {
|
|
384
|
+
releaseBrowser();
|
|
385
|
+
await options.handlers.onDisconnect();
|
|
386
|
+
},
|
|
264
387
|
requestBrowserAction: createBrowserActionHandler({
|
|
265
388
|
frame,
|
|
266
389
|
hostWindow: window,
|
|
@@ -305,11 +428,11 @@ export async function oysterAppCapabilities(options) {
|
|
|
305
428
|
let leaseExpiresAt = 0;
|
|
306
429
|
return new TerminalCapability((input) => transport("open", input), {
|
|
307
430
|
computerId: config.computerId,
|
|
308
|
-
terminalUrl: selected.terminalUrl,
|
|
431
|
+
terminalUrl: computerTargetUrl(selected.terminalUrl, options.computerTarget),
|
|
309
432
|
hostWindow: window,
|
|
310
433
|
mountSignal: options.signal,
|
|
311
434
|
ensureAccess: Object.assign(async (signal) => {
|
|
312
|
-
const response = await fetch(`/apps/${encodeURIComponent(config.installationId)}/`, {
|
|
435
|
+
const response = await fetch(computerTargetUrl(`/apps/${encodeURIComponent(config.installationId)}/`, options.computerTarget), {
|
|
313
436
|
headers: { Accept: "application/json" },
|
|
314
437
|
credentials: "same-origin",
|
|
315
438
|
cache: "no-store",
|
|
@@ -325,11 +448,18 @@ export async function oysterAppCapabilities(options) {
|
|
|
325
448
|
retryable: false,
|
|
326
449
|
});
|
|
327
450
|
leaseExpiresAt = Date.now() + 60_000;
|
|
328
|
-
|
|
451
|
+
const selected = terminalSelection(current);
|
|
452
|
+
return {
|
|
453
|
+
...selected,
|
|
454
|
+
terminalUrl: selected.terminalUrl === null
|
|
455
|
+
? null
|
|
456
|
+
: computerTargetUrl(selected.terminalUrl, options.computerTarget),
|
|
457
|
+
};
|
|
329
458
|
}, { leaseExpiresAt: () => leaseExpiresAt }),
|
|
330
459
|
});
|
|
331
460
|
},
|
|
332
461
|
},
|
|
462
|
+
{ contract: approvals },
|
|
333
463
|
{ contract: agentList },
|
|
334
464
|
{ contract: agentManage },
|
|
335
465
|
{ contract: agentSessionRead },
|
|
@@ -357,7 +487,7 @@ export async function oysterAppCapabilities(options) {
|
|
|
357
487
|
return [
|
|
358
488
|
Promise.resolve({
|
|
359
489
|
...appsCall,
|
|
360
|
-
handle: new Calls(base, options.signal),
|
|
490
|
+
handle: new Calls(base, options.signal, options.computerTarget),
|
|
361
491
|
}),
|
|
362
492
|
];
|
|
363
493
|
}
|
|
@@ -368,14 +498,20 @@ export async function oysterAppCapabilities(options) {
|
|
|
368
498
|
return [];
|
|
369
499
|
}
|
|
370
500
|
return [
|
|
371
|
-
registeredCapabilityHandle(registration, descriptor, config.installationId).then((handle) => ({ ...registration.contract, handle })),
|
|
501
|
+
registeredCapabilityHandle(registration, descriptor, config.installationId, options.computerTarget).then((handle) => ({ ...registration.contract, handle })),
|
|
372
502
|
];
|
|
373
503
|
}));
|
|
374
504
|
if (unknownCapabilities.length > 0)
|
|
375
505
|
document.body.dataset.oysterCapability = unknownCapabilities.join(", ");
|
|
376
506
|
const resolved = [
|
|
377
|
-
{
|
|
378
|
-
|
|
507
|
+
{
|
|
508
|
+
...appBackend,
|
|
509
|
+
handle: new Backend(computerTargetUrl(config.backendUrl, options.computerTarget)),
|
|
510
|
+
},
|
|
511
|
+
{
|
|
512
|
+
...appMethods,
|
|
513
|
+
handle: new Methods(config.methodsUrl, options.computerTarget),
|
|
514
|
+
},
|
|
379
515
|
...capabilities,
|
|
380
516
|
...(config.appsOpen && options.appOpen
|
|
381
517
|
? [{ ...appsOpen, handle: new AppOpen(options.appOpen) }]
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { HTTPException } from "hono/http-exception";
|
|
2
|
+
/**
|
|
3
|
+
* Project the generated module's fields without exposing its private module name.
|
|
4
|
+
* The generated module remains the sole page-source contract.
|
|
5
|
+
* @inline
|
|
6
|
+
*/
|
|
7
|
+
export type PageAssetSources = {
|
|
8
|
+
[Key in keyof typeof import("#page-script")]: (typeof import("#page-script"))[Key];
|
|
9
|
+
};
|
|
10
|
+
/**
|
|
11
|
+
* Acquire page sources only when a host page or asset request needs them.
|
|
12
|
+
*/
|
|
13
|
+
export type PageAssetLoader = () => Promise<PageAssetSources>;
|
|
14
|
+
/** A failed acquisition refuses this request without exposing loader details. */
|
|
15
|
+
export declare class PageAssetsLoadError extends HTTPException {
|
|
16
|
+
constructor(cause: unknown);
|
|
17
|
+
getResponse(): Response;
|
|
18
|
+
}
|
|
19
|
+
/**
|
|
20
|
+
* Missing page sources must fail the request, never produce an empty script.
|
|
21
|
+
*/
|
|
22
|
+
export declare const loadPageAssets: PageAssetLoader;
|