@fungi.computer/oyster 0.2.0 → 0.3.1
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 +396 -72
- package/dist/agents.d.ts +3 -2
- package/dist/agents.js +4 -2
- package/dist/app-builds.d.ts +81 -8
- package/dist/app-builds.js +213 -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 +20 -6
- package/dist/apps/gate.js +42 -16
- package/dist/apps/index.d.ts +3 -1
- package/dist/apps/index.js +3 -1
- package/dist/apps/installation.d.ts +132 -51
- package/dist/apps/installation.js +176 -70
- 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 +8375 -334
- 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.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 +102 -4
- package/dist/frame-binding-wire-script.js +1 -1
- package/dist/frame-binding-wire.js +4 -2
- package/dist/git-auth.d.ts +0 -2
- package/dist/git-auth.js +0 -2
- 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 +25 -15
- package/dist/index.js +39 -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/older-oyster-store-refusal.d.ts +4 -0
- package/dist/older-oyster-store-refusal.js +18 -0
- package/dist/older-oyster-store.d.ts +21 -0
- package/dist/older-oyster-store.js +43 -0
- 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 -3
- 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 +68 -9
- package/dist/standalone/agents.js +428 -56
- package/dist/standalone/app-builds-binding.d.ts +20 -2
- package/dist/standalone/app-builds-binding.js +147 -23
- package/dist/standalone/app-set.d.ts +5 -2
- package/dist/standalone/app-set.js +186 -144
- 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 +129 -22
- 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 +37 -10
- package/dist/standalone/registry.d.ts +151 -9
- package/dist/standalone/registry.js +305 -8
- package/dist/standalone/worker.d.ts +19 -5
- package/dist/standalone/worker.js +90 -54
- package/dist/standalone/workflow-binding.d.ts +18 -22
- package/dist/standalone/workflow-binding.js +18 -18
- 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 +162 -161
- package/dist/standalone/workflow-host.js +101 -21
- 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 +65 -5
- 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 +140 -43
- 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 +64 -52
- package/dist/workers.d.ts +226 -13
- package/dist/workers.js +363 -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 +1594 -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 -71
- package/dist/installation-consent.d.ts +0 -81
- package/dist/installation-consent.js +0 -84
- package/dist/shell/app-directory.d.ts +0 -542
- package/dist/shell/app-directory.js +0 -196
- 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 -128
- package/dist/shell/window-manager.js +0 -730
- 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 -352
- package/dist/shell/workbench-model.d.ts +0 -194
- 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
package/dist/pack.js
CHANGED
|
@@ -4,11 +4,13 @@ import { join, relative, resolve } from "node:path";
|
|
|
4
4
|
import * as Effect from "effect/Effect";
|
|
5
5
|
import * as Schema from "effect/Schema";
|
|
6
6
|
import { releaseSchema } from "@fungi.computer/puffball/effect";
|
|
7
|
+
import { installOnOyster, installationObservationSignal, completedInstallationResponse, } from "./installation-client.js";
|
|
7
8
|
import { parseArtifactRef, parseReleaseId, parseSha256Digest, parseSourceRevision, } from "@fungi.computer/puffball";
|
|
8
9
|
import { commitPreparedCandidate, createAppCandidatePreparer, createArtifactsSourceCapture, createFirstPartyAppSourceDistribution, createLocalArtifactWriter, createLocalBuildExecutor, createLocalCommitSourceCapture, normalizeAppPublisherRecipe, } from "./build.js";
|
|
9
10
|
import { templateSeedClaimSchema, } from "./source/template-seed.js";
|
|
10
11
|
import { pushTemplateSource } from "./source/template-push.js";
|
|
11
12
|
import { sha256 } from "./release.js";
|
|
13
|
+
export { InstallationObservationError } from "./installation-client.js";
|
|
12
14
|
async function filesUnder(directory) {
|
|
13
15
|
const entries = await readdir(directory, {
|
|
14
16
|
recursive: true,
|
|
@@ -26,9 +28,9 @@ async function packCapturedSource(options, sourceCapture) {
|
|
|
26
28
|
const preparer = createAppCandidatePreparer({
|
|
27
29
|
config: { appId: options.appId, recipe: options.recipe },
|
|
28
30
|
sourceCapture,
|
|
29
|
-
buildExecutor: createLocalBuildExecutor(
|
|
30
|
-
|
|
31
|
-
|
|
31
|
+
buildExecutor: createLocalBuildExecutor(options.packageStoreDirectory === undefined
|
|
32
|
+
? {}
|
|
33
|
+
: { packageStoreDirectory: options.packageStoreDirectory }),
|
|
32
34
|
});
|
|
33
35
|
const prepared = await Effect.runPromise(Effect.scoped(preparer.prepareCandidate(parseReleaseId(options.releaseId ??
|
|
34
36
|
`rel_${revision.slice("git:".length, "git:".length + 12)}`))));
|
|
@@ -52,27 +54,40 @@ export async function packRelease(options) {
|
|
|
52
54
|
* Capture an Artifacts Git commit, build its Release, upload it to an Oyster,
|
|
53
55
|
* and install it, or switch an existing installation to it
|
|
54
56
|
* ({@link installRelease}).
|
|
57
|
+
* `waitMs` starts with the upload/observation phase after packing; a supplied
|
|
58
|
+
* caller signal retains its original lifetime through that phase.
|
|
55
59
|
*/
|
|
56
60
|
export async function installFromSource(input) {
|
|
57
61
|
const outDirectory = await mkdtemp(join(tmpdir(), "oyster-source-release-"));
|
|
58
62
|
try {
|
|
59
|
-
|
|
63
|
+
let options = {
|
|
60
64
|
appId: input.appId,
|
|
61
|
-
releaseId: input.releaseId,
|
|
62
65
|
recipe: input.recipe,
|
|
63
66
|
sourceRevision: input.source.sourceRevision,
|
|
64
|
-
packageStoreDirectory: input.packageStoreDirectory,
|
|
65
67
|
outDirectory,
|
|
66
|
-
}
|
|
68
|
+
};
|
|
69
|
+
if (input.releaseId !== undefined)
|
|
70
|
+
options = { ...options, releaseId: input.releaseId };
|
|
71
|
+
if (input.packageStoreDirectory !== undefined)
|
|
72
|
+
options = {
|
|
73
|
+
...options,
|
|
74
|
+
packageStoreDirectory: input.packageStoreDirectory,
|
|
75
|
+
};
|
|
76
|
+
await packCapturedSource(options, createArtifactsSourceCapture({
|
|
67
77
|
...input.source,
|
|
68
78
|
sourceRevision: parseSourceRevision(input.source.sourceRevision),
|
|
69
79
|
}));
|
|
70
|
-
|
|
80
|
+
let installation = {
|
|
71
81
|
directory: outDirectory,
|
|
72
82
|
url: input.url,
|
|
73
83
|
key: input.key,
|
|
74
84
|
installationId: input.installationId,
|
|
75
|
-
}
|
|
85
|
+
};
|
|
86
|
+
if (input.waitMs !== undefined)
|
|
87
|
+
installation = { ...installation, waitMs: input.waitMs };
|
|
88
|
+
if (input.signal !== undefined)
|
|
89
|
+
installation = { ...installation, signal: input.signal };
|
|
90
|
+
return await installRelease(installation);
|
|
76
91
|
}
|
|
77
92
|
finally {
|
|
78
93
|
await rm(outDirectory, { recursive: true, force: true });
|
|
@@ -81,9 +96,16 @@ export async function installFromSource(input) {
|
|
|
81
96
|
/**
|
|
82
97
|
* Upload a packed Release to an Oyster and install it. The host installs a
|
|
83
98
|
* new installation (201) or switches an existing one to this Release (200),
|
|
84
|
-
* keeping its state;
|
|
99
|
+
* keeping its state. Uploads precede admission; this helper then waits for the
|
|
100
|
+
* correlated terminal owner receipt, not merely the HTTP acknowledgement.
|
|
101
|
+
* Uploads and observation share the caller signal or explicit `waitMs`; absent
|
|
102
|
+
* either, the installer owns a twenty-minute default. Expiry does not cancel
|
|
103
|
+
* admitted work and is reported as an unconfirmed observation, not refusal.
|
|
85
104
|
*/
|
|
86
105
|
export async function installRelease(input) {
|
|
106
|
+
// One installer budget for uploads + observation, not an App authority grant.
|
|
107
|
+
const signal = installationObservationSignal(input);
|
|
108
|
+
signal.throwIfAborted();
|
|
87
109
|
const directory = resolve(input.directory);
|
|
88
110
|
const release = Schema.decodeUnknownSync(Schema.fromJsonString(releaseSchema))(await readFile(join(directory, "release.json"), "utf8"));
|
|
89
111
|
const authorization = { Authorization: `Bearer ${input.key}` };
|
|
@@ -91,18 +113,21 @@ export async function installRelease(input) {
|
|
|
91
113
|
const bytes = new Uint8Array(await readFile(file));
|
|
92
114
|
const artifactRef = `artifact:${relative(join(directory, "artifacts"), file)}`;
|
|
93
115
|
const digest = parseSha256Digest(await sha256(bytes));
|
|
94
|
-
const response = await fetch(new URL(`/api/artifacts/${encodeURIComponent(artifactRef)}?digest=${digest}`, input.url), { method: "PUT", headers: authorization, body: bytes });
|
|
116
|
+
const response = await fetch(new URL(`/api/artifacts/${encodeURIComponent(artifactRef)}?digest=${digest}`, input.url), { method: "PUT", headers: authorization, body: bytes, signal });
|
|
95
117
|
if (!response.ok)
|
|
96
118
|
throw new Error(`Artifact upload failed (${String(response.status)}): ${artifactRef}`);
|
|
97
119
|
}
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
120
|
+
let installation = {
|
|
121
|
+
url: input.url,
|
|
122
|
+
key: input.key,
|
|
123
|
+
release,
|
|
124
|
+
installationId: input.installationId ?? release.appId.replaceAll("_", "-"),
|
|
125
|
+
signal,
|
|
126
|
+
};
|
|
127
|
+
if (input.onAdmitted !== undefined)
|
|
128
|
+
installation = { ...installation, onAdmitted: input.onAdmitted };
|
|
129
|
+
const operation = await installOnOyster(installation);
|
|
130
|
+
return completedInstallationResponse(operation);
|
|
106
131
|
}
|
|
107
132
|
/** Project one exact first-party commit's editable source, `git push` it to
|
|
108
133
|
* an Oyster's own template for that App, and claim the pushed head, so
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import type { RequestShellAppAccess } from "@fungi.computer/nacre/shell-module";
|
|
2
|
+
import type { WorkbenchDirectory } from "@fungi.computer/nacre/workbench-directory";
|
|
3
|
+
/**
|
|
4
|
+
* Keep standalone consent in the Oyster host: the picked shell draws the
|
|
5
|
+
* prompt, while Oyster bounds the request and commits through its owner CAS.
|
|
6
|
+
*/
|
|
7
|
+
export declare function createShellAppAccess(directory: () => Promise<WorkbenchDirectory>): RequestShellAppAccess;
|
|
@@ -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;
|
|
@@ -32,7 +46,7 @@ export declare const oysterAppConfigSchema: Schema.Struct<{
|
|
|
32
46
|
help?: Readonly<{
|
|
33
47
|
order?: number | undefined;
|
|
34
48
|
}> | undefined;
|
|
35
|
-
menu
|
|
49
|
+
menu?: Readonly<{
|
|
36
50
|
order?: number | undefined;
|
|
37
51
|
}> | undefined;
|
|
38
52
|
palette?: Readonly<{
|
|
@@ -64,7 +78,7 @@ export declare const oysterAppConfigSchema: Schema.Struct<{
|
|
|
64
78
|
help?: Readonly<{
|
|
65
79
|
order?: number | undefined;
|
|
66
80
|
}> | undefined;
|
|
67
|
-
menu
|
|
81
|
+
menu?: Readonly<{
|
|
68
82
|
order?: number | undefined;
|
|
69
83
|
}> | undefined;
|
|
70
84
|
palette?: Readonly<{
|
|
@@ -141,6 +155,12 @@ export type OysterAppConfig = typeof oysterAppConfigSchema.Type;
|
|
|
141
155
|
/** One isolated App connection, with the slots this host admitted for it. */
|
|
142
156
|
export type OysterAppConnectionOptions = Readonly<{
|
|
143
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;
|
|
144
164
|
frame: HTMLIFrameElement;
|
|
145
165
|
signal: AbortSignal;
|
|
146
166
|
handlers: AppHostHandlers;
|
|
@@ -156,4 +176,4 @@ export declare function connectOysterApp(options: OysterAppConnectionOptions): P
|
|
|
156
176
|
export { oysterStorageHandle } from "./storage.js";
|
|
157
177
|
export { appSandbox } from "../app-sandbox.js";
|
|
158
178
|
/** Resolve only the slots admitted for this installation by its current host. */
|
|
159
|
-
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"]>;
|