@fungi.computer/truffle 0.0.0-stage → 0.2.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/LICENSE +21 -0
- package/README.md +542 -2
- package/computerd-loopback.cjs +16 -0
- package/dist/auth-cli.d.ts +46 -0
- package/dist/auth-cli.js +373 -0
- package/dist/fungi-account-command.d.ts +18 -0
- package/dist/fungi-account-command.js +95 -0
- package/dist/index.d.ts +27 -0
- package/dist/index.js +23 -0
- package/dist/internal/image-fit-metafile.json +106 -0
- package/dist/internal/local-image-fit-worker.mjs +960 -0
- package/dist/internal/private-workerd-sandbox-worker.mjs +27487 -0
- package/dist/internal/sandbox-metafile.json +6726 -0
- package/dist/linear-auth.d.ts +4 -0
- package/dist/linear-auth.js +72 -0
- package/dist/local-archive-handoff.d.ts +12 -0
- package/dist/local-archive-handoff.js +21 -0
- package/dist/local-extensions.d.ts +143 -0
- package/dist/local-extensions.js +624 -0
- package/dist/local-image-fit.d.ts +30 -0
- package/dist/local-image-fit.js +105 -0
- package/dist/local-login.d.ts +55 -0
- package/dist/local-login.js +167 -0
- package/dist/local-models.d.ts +16 -0
- package/dist/local-models.js +82 -0
- package/dist/local-session-archives.d.ts +8 -0
- package/dist/local-session-archives.js +152 -0
- package/dist/local-skill-source.d.ts +21 -0
- package/dist/local-skill-source.js +433 -0
- package/dist/log-event.d.ts +9 -0
- package/dist/log-event.js +13 -0
- package/dist/private-bounded-response.d.ts +2 -0
- package/dist/private-bounded-response.js +41 -0
- package/dist/private-bun-shiitake-host.d.ts +11 -0
- package/dist/private-bun-shiitake-host.js +66 -0
- package/dist/private-file-credential-store.d.ts +58 -0
- package/dist/private-file-credential-store.js +331 -0
- package/dist/private-files-patch.d.ts +4 -0
- package/dist/private-files-patch.js +104 -0
- package/dist/private-fungi-account.d.ts +252 -0
- package/dist/private-fungi-account.js +1329 -0
- package/dist/private-fungi-auth-error.d.ts +5 -0
- package/dist/private-fungi-auth-error.js +7 -0
- package/dist/private-fungi-mcp-connection.d.ts +26 -0
- package/dist/private-fungi-mcp-connection.js +162 -0
- package/dist/private-fungi-mcp-endpoint.d.ts +2 -0
- package/dist/private-fungi-mcp-endpoint.js +15 -0
- package/dist/private-fungi-mcp-selection-schema.d.ts +7 -0
- package/dist/private-fungi-mcp-selection-schema.js +15 -0
- package/dist/private-fungi-mcp-selection.d.ts +14 -0
- package/dist/private-fungi-mcp-selection.js +49 -0
- package/dist/private-fungi-model-api.d.ts +6 -0
- package/dist/private-fungi-model-api.js +125 -0
- package/dist/private-fungi-model-catalog.d.ts +16 -0
- package/dist/private-fungi-model-catalog.js +86 -0
- package/dist/private-fungi-model-provider.d.ts +6 -0
- package/dist/private-fungi-model-provider.js +51 -0
- package/dist/private-fungi-pending-logouts.d.ts +34 -0
- package/dist/private-fungi-pending-logouts.js +135 -0
- package/dist/private-fungi-service-logout.d.ts +44 -0
- package/dist/private-fungi-service-logout.js +59 -0
- package/dist/private-invocation-computer.d.ts +58 -0
- package/dist/private-invocation-computer.js +498 -0
- package/dist/private-invocation-files.d.ts +30 -0
- package/dist/private-invocation-files.js +520 -0
- package/dist/private-local-acp-stdio.d.ts +17 -0
- package/dist/private-local-acp-stdio.js +145 -0
- package/dist/private-local-admission-gate.d.ts +14 -0
- package/dist/private-local-admission-gate.js +52 -0
- package/dist/private-local-agent-control.d.ts +101 -0
- package/dist/private-local-agent-control.js +30 -0
- package/dist/private-local-agent-default-model.d.ts +12 -0
- package/dist/private-local-agent-default-model.js +31 -0
- package/dist/private-local-auth-rpc.d.ts +129 -0
- package/dist/private-local-auth-rpc.js +396 -0
- package/dist/private-local-bwrap.d.ts +13 -0
- package/dist/private-local-bwrap.js +70 -0
- package/dist/private-local-client-version.d.ts +5 -0
- package/dist/private-local-client-version.js +43 -0
- package/dist/private-local-computer-attach.d.ts +49 -0
- package/dist/private-local-computer-attach.js +230 -0
- package/dist/private-local-computer-backend.d.ts +282 -0
- package/dist/private-local-computer-backend.js +402 -0
- package/dist/private-local-computer-processes.d.ts +202 -0
- package/dist/private-local-computer-processes.js +613 -0
- package/dist/private-local-config-root.d.ts +21 -0
- package/dist/private-local-config-root.js +19 -0
- package/dist/private-local-containment.d.ts +51 -0
- package/dist/private-local-containment.js +161 -0
- package/dist/private-local-default-model.d.ts +11 -0
- package/dist/private-local-default-model.js +45 -0
- package/dist/private-local-delegation.d.ts +36 -0
- package/dist/private-local-delegation.js +133 -0
- package/dist/private-local-effective-model.d.ts +27 -0
- package/dist/private-local-effective-model.js +17 -0
- package/dist/private-local-exec-environment.d.ts +16 -0
- package/dist/private-local-exec-environment.js +11 -0
- package/dist/private-local-exec-launch.d.ts +41 -0
- package/dist/private-local-exec-launch.js +51 -0
- package/dist/private-local-exec-path.d.ts +6 -0
- package/dist/private-local-exec-path.js +40 -0
- package/dist/private-local-git-exclude.d.ts +2 -0
- package/dist/private-local-git-exclude.js +33 -0
- package/dist/private-local-http-dispatch.d.ts +5 -0
- package/dist/private-local-http-dispatch.js +14 -0
- package/dist/private-local-linux-sandbox.d.ts +8 -0
- package/dist/private-local-linux-sandbox.js +67 -0
- package/dist/private-local-mail.d.ts +13 -0
- package/dist/private-local-mail.js +77 -0
- package/dist/private-local-mesh-enrollment.d.ts +34 -0
- package/dist/private-local-mesh-enrollment.js +188 -0
- package/dist/private-local-mesh-links.d.ts +21 -0
- package/dist/private-local-mesh-links.js +229 -0
- package/dist/private-local-mesh-record.d.ts +120 -0
- package/dist/private-local-mesh-record.js +55 -0
- package/dist/private-local-process-identity.d.ts +20 -0
- package/dist/private-local-process-identity.js +40 -0
- package/dist/private-local-runtime-host.d.ts +192 -0
- package/dist/private-local-runtime-host.js +1177 -0
- package/dist/private-local-sandbox-policy.d.ts +21 -0
- package/dist/private-local-sandbox-policy.js +121 -0
- package/dist/private-local-sandbox.d.ts +37 -0
- package/dist/private-local-sandbox.js +114 -0
- package/dist/private-local-seatbelt.d.ts +24 -0
- package/dist/private-local-seatbelt.js +68 -0
- package/dist/private-local-service-contract.d.ts +170 -0
- package/dist/private-local-service-contract.js +1 -0
- package/dist/private-local-service-discovery.d.ts +217 -0
- package/dist/private-local-service-discovery.js +322 -0
- package/dist/private-local-service-ids.d.ts +3 -0
- package/dist/private-local-service-ids.js +8 -0
- package/dist/private-local-service-lease.d.ts +16 -0
- package/dist/private-local-service-lease.js +105 -0
- package/dist/private-local-service-listener.d.ts +22 -0
- package/dist/private-local-service-listener.js +407 -0
- package/dist/private-local-service-log.d.ts +128 -0
- package/dist/private-local-service-log.js +321 -0
- package/dist/private-local-service-owner.d.ts +135 -0
- package/dist/private-local-service-owner.js +1369 -0
- package/dist/private-local-service-process.d.ts +1 -0
- package/dist/private-local-service-process.js +8 -0
- package/dist/private-local-service-termination.d.ts +7 -0
- package/dist/private-local-service-termination.js +67 -0
- package/dist/private-local-service.d.ts +46 -0
- package/dist/private-local-service.js +606 -0
- package/dist/private-local-session-discovery.d.ts +66 -0
- package/dist/private-local-session-discovery.js +278 -0
- package/dist/private-local-shiitake-host.d.ts +44 -0
- package/dist/private-local-shiitake-host.js +143 -0
- package/dist/private-local-startup-model.d.ts +51 -0
- package/dist/private-local-startup-model.js +86 -0
- package/dist/private-local-stop-all.d.ts +27 -0
- package/dist/private-local-stop-all.js +26 -0
- package/dist/private-local-ui-attachment.d.ts +19 -0
- package/dist/private-local-ui-attachment.js +71 -0
- package/dist/private-local-worker-server.d.ts +16 -0
- package/dist/private-local-worker-server.js +144 -0
- package/dist/private-mcp-form-contract.d.ts +186 -0
- package/dist/private-mcp-form-contract.js +53 -0
- package/dist/private-mcp-form-elicitation.d.ts +18 -0
- package/dist/private-mcp-form-elicitation.js +97 -0
- package/dist/private-mcp-form-session.d.ts +172 -0
- package/dist/private-mcp-form-session.js +148 -0
- package/dist/private-openai-claims.d.ts +30 -0
- package/dist/private-openai-claims.js +69 -0
- package/dist/private-oyster-mcp-auth.d.ts +13 -0
- package/dist/private-oyster-mcp-auth.js +57 -0
- package/dist/private-provider-rejection.d.ts +15 -0
- package/dist/private-provider-rejection.js +24 -0
- package/dist/private-sqlite-owner.d.ts +39 -0
- package/dist/private-sqlite-owner.js +169 -0
- package/dist/private-team-computers.d.ts +38 -0
- package/dist/private-team-computers.js +110 -0
- package/dist/private-workerd-sandbox.d.ts +3 -0
- package/dist/private-workerd-sandbox.js +347 -0
- package/dist/seatbelt/safe-base.sbpl +79 -0
- package/dist/subscription-activation.d.ts +9 -0
- package/dist/subscription-activation.js +62 -0
- package/dist/subscription-identity.d.ts +23 -0
- package/dist/subscription-identity.js +124 -0
- package/dist/subscription-turn.d.ts +17 -0
- package/dist/subscription-turn.js +143 -0
- package/dist/team-apps.d.ts +24 -0
- package/dist/team-apps.js +128 -0
- package/dist/tracker-git-credential.d.ts +15 -0
- package/dist/tracker-git-credential.js +87 -0
- package/native/bin/LICENSE.bubblewrap +502 -0
- package/native/bin/LICENSE.libcap +112 -0
- package/native/bin/LICENSE.sandbox-runtime +201 -0
- package/native/bin/SOURCE.bubblewrap.tar.xz +0 -0
- package/native/bin/build.json +4037 -0
- package/native/bin/bwrap-build-source/Dockerfile +48 -0
- package/native/bin/bwrap-build-source/README.md +96 -0
- package/native/bin/bwrap-build-source/build.mjs +52 -0
- package/native/bin/bwrap-build-source/linux-arm64.ini +14 -0
- package/native/bin/bwrap-build-source/pin.mjs +21 -0
- package/native/bin/bwrap-build-source/record.mjs +74 -0
- package/native/bin/bwrap-build-source/vendor/LICENSE.sandbox-runtime +201 -0
- package/native/bin/bwrap-build-source/vendor/README.md +23 -0
- package/native/bin/bwrap-build-source/vendor/seccomp-unix-block.c +280 -0
- package/native/bin/bwrap-build.json +44 -0
- package/native/bin/fungi-apply-patch +0 -0
- package/native/bin/fungi-bwrap +0 -0
- package/native/bin/fungi-bwrap.sha256 +1 -0
- package/native/bin/fungi-seccomp.bpf +0 -0
- package/native/bin/fungi-seccomp.bpf.sha256 +1 -0
- package/package.json +161 -3
|
@@ -0,0 +1,396 @@
|
|
|
1
|
+
import { RpcTarget } from "@fungi.computer/capnweb";
|
|
2
|
+
import { z } from "zod";
|
|
3
|
+
import { fungiAccountLogoutRequestSchema, fungiAccountLogoutResultSchema, } from "./private-fungi-service-logout.js";
|
|
4
|
+
import { LocalAuthRefusal, localAuthInputLimit, runLocalAuthCli, } from "./auth-cli.js";
|
|
5
|
+
import { openAIIdentityDetailSchema } from "./private-openai-claims.js";
|
|
6
|
+
import { CredentialActivationError } from "./subscription-activation.js";
|
|
7
|
+
import { OpenAIIdentityUnavailable } from "./subscription-identity.js";
|
|
8
|
+
const requestSchema = z.object({
|
|
9
|
+
command: z.enum(["login", "logout", "status"]),
|
|
10
|
+
provider: z.string().optional(),
|
|
11
|
+
authType: z.enum(["oauth", "api_key"]).optional(),
|
|
12
|
+
method: z.string().optional(),
|
|
13
|
+
});
|
|
14
|
+
const authStatusSchema = z.discriminatedUnion("state", [
|
|
15
|
+
z.object({
|
|
16
|
+
providerId: z.string(),
|
|
17
|
+
providerName: z.string(),
|
|
18
|
+
state: z.literal("unconfigured"),
|
|
19
|
+
}),
|
|
20
|
+
z.object({
|
|
21
|
+
providerId: z.string(),
|
|
22
|
+
providerName: z.string(),
|
|
23
|
+
state: z.literal("configured"),
|
|
24
|
+
type: z.enum(["oauth", "api_key"]),
|
|
25
|
+
}),
|
|
26
|
+
]);
|
|
27
|
+
const authResultSchema = z.discriminatedUnion("kind", [
|
|
28
|
+
z.object({ kind: z.literal("completed") }),
|
|
29
|
+
z.object({
|
|
30
|
+
kind: z.literal("status"),
|
|
31
|
+
providers: z.array(authStatusSchema),
|
|
32
|
+
}),
|
|
33
|
+
]);
|
|
34
|
+
// Only Pi's serializable presentation crosses RPC; AbortSignals stay local.
|
|
35
|
+
const promptSchema = z.discriminatedUnion("type", [
|
|
36
|
+
z.object({
|
|
37
|
+
type: z.literal("text"),
|
|
38
|
+
message: z.string(),
|
|
39
|
+
placeholder: z.string().optional(),
|
|
40
|
+
}),
|
|
41
|
+
z.object({
|
|
42
|
+
type: z.literal("secret"),
|
|
43
|
+
message: z.string(),
|
|
44
|
+
placeholder: z.string().optional(),
|
|
45
|
+
}),
|
|
46
|
+
z.object({
|
|
47
|
+
type: z.literal("manual_code"),
|
|
48
|
+
message: z.string(),
|
|
49
|
+
placeholder: z.string().optional(),
|
|
50
|
+
}),
|
|
51
|
+
z.object({
|
|
52
|
+
type: z.literal("select"),
|
|
53
|
+
message: z.string(),
|
|
54
|
+
options: z.array(z.object({
|
|
55
|
+
id: z.string(),
|
|
56
|
+
label: z.string(),
|
|
57
|
+
description: z.string().optional(),
|
|
58
|
+
})),
|
|
59
|
+
}),
|
|
60
|
+
]);
|
|
61
|
+
const linkSchema = z.object({ url: z.string(), label: z.string().optional() });
|
|
62
|
+
const eventSchema = z.discriminatedUnion("type", [
|
|
63
|
+
z.object({
|
|
64
|
+
type: z.literal("info"),
|
|
65
|
+
message: z.string(),
|
|
66
|
+
links: z.array(linkSchema).optional(),
|
|
67
|
+
}),
|
|
68
|
+
z.object({ type: z.literal("progress"), message: z.string() }),
|
|
69
|
+
z.object({
|
|
70
|
+
type: z.literal("auth_url"),
|
|
71
|
+
url: z.string(),
|
|
72
|
+
instructions: z.string().optional(),
|
|
73
|
+
}),
|
|
74
|
+
z.object({
|
|
75
|
+
type: z.literal("device_code"),
|
|
76
|
+
userCode: z.string(),
|
|
77
|
+
verificationUri: z.string(),
|
|
78
|
+
intervalSeconds: z.number().optional(),
|
|
79
|
+
expiresInSeconds: z.number().optional(),
|
|
80
|
+
}),
|
|
81
|
+
]);
|
|
82
|
+
const answerSchema = z.string().max(localAuthInputLimit);
|
|
83
|
+
/**
|
|
84
|
+
* The closed set of failures that cross the service. Provider errors can
|
|
85
|
+
* contain credential material, so no free text, cause or provider message
|
|
86
|
+
* crosses: only a fixed category, HTTP status, or missing-person claim NAMES.
|
|
87
|
+
*/
|
|
88
|
+
const localAuthFailureSchema = z.discriminatedUnion("category", [
|
|
89
|
+
z.strictObject({
|
|
90
|
+
category: z.literal("identity-unavailable"),
|
|
91
|
+
detail: openAIIdentityDetailSchema.optional(),
|
|
92
|
+
}),
|
|
93
|
+
z.strictObject({ category: z.literal("callback-port-in-use") }),
|
|
94
|
+
z.strictObject({ category: z.literal("oauth-state-mismatch") }),
|
|
95
|
+
z.strictObject({
|
|
96
|
+
category: z.literal("token-exchange-rejected"),
|
|
97
|
+
status: z.number().int().min(100).max(599),
|
|
98
|
+
}),
|
|
99
|
+
z.strictObject({ category: z.literal("cancelled") }),
|
|
100
|
+
z.strictObject({ category: z.literal("no-key-entered") }),
|
|
101
|
+
z.strictObject({ category: z.literal("unknown") }),
|
|
102
|
+
]);
|
|
103
|
+
const wireResultSchema = z.union([
|
|
104
|
+
authResultSchema,
|
|
105
|
+
z.strictObject({
|
|
106
|
+
kind: z.literal("failed"),
|
|
107
|
+
failure: localAuthFailureSchema,
|
|
108
|
+
}),
|
|
109
|
+
]);
|
|
110
|
+
/** Pi 1.3.0's fixed error prefixes; never retain a provider body suffix. */
|
|
111
|
+
const callbackPortInUse = /^Port \d+ is in use\b/u;
|
|
112
|
+
const oauthStateMismatch = "OAuth state mismatch";
|
|
113
|
+
const openAITokenRequestFailed = /^OpenAI OAuth token request failed \(([1-5]\d{2})\)/u;
|
|
114
|
+
function classifyFailure(cause, aborted) {
|
|
115
|
+
if (aborted)
|
|
116
|
+
return { category: "cancelled" };
|
|
117
|
+
if (cause instanceof LocalAuthRefusal)
|
|
118
|
+
return { category: "no-key-entered" };
|
|
119
|
+
if (cause instanceof OpenAIIdentityUnavailable)
|
|
120
|
+
return { category: "identity-unavailable", detail: cause.detail };
|
|
121
|
+
if (cause instanceof CredentialActivationError &&
|
|
122
|
+
cause.category === "identity-unavailable")
|
|
123
|
+
return { category: "identity-unavailable" };
|
|
124
|
+
if (cause instanceof Error) {
|
|
125
|
+
const tokenRequest = openAITokenRequestFailed.exec(cause.message);
|
|
126
|
+
if (tokenRequest !== null)
|
|
127
|
+
return {
|
|
128
|
+
category: "token-exchange-rejected",
|
|
129
|
+
status: Number(tokenRequest[1]),
|
|
130
|
+
};
|
|
131
|
+
}
|
|
132
|
+
if (cause instanceof Error && callbackPortInUse.test(cause.message))
|
|
133
|
+
return { category: "callback-port-in-use" };
|
|
134
|
+
if (cause instanceof Error && cause.message === oauthStateMismatch)
|
|
135
|
+
return { category: "oauth-state-mismatch" };
|
|
136
|
+
return { category: "unknown" };
|
|
137
|
+
}
|
|
138
|
+
/** Fixed wording per category; the dialog shows exactly this. */
|
|
139
|
+
function failureText(failure) {
|
|
140
|
+
switch (failure.category) {
|
|
141
|
+
case "identity-unavailable": {
|
|
142
|
+
const detail = failure.detail;
|
|
143
|
+
if (detail === undefined)
|
|
144
|
+
return "Sign-in could not establish the account identity (identity-unavailable). Nothing was saved.";
|
|
145
|
+
const auth = detail.authClaimNames === undefined
|
|
146
|
+
? ""
|
|
147
|
+
: "; auth claims: " + (detail.authClaimNames.join(", ") || "none");
|
|
148
|
+
return ("OpenAI sign-in did not identify the person (identity-unavailable: " +
|
|
149
|
+
detail.claim +
|
|
150
|
+
" " +
|
|
151
|
+
detail.problem +
|
|
152
|
+
"; claims: " +
|
|
153
|
+
(detail.claimNames.join(", ") || "none") +
|
|
154
|
+
auth +
|
|
155
|
+
"). Nothing was saved.");
|
|
156
|
+
}
|
|
157
|
+
case "callback-port-in-use":
|
|
158
|
+
return "The sign-in callback port is in use, probably by another sign-in or the Codex CLI. Close it and try again.";
|
|
159
|
+
case "oauth-state-mismatch":
|
|
160
|
+
return "The sign-in redirect did not belong to this attempt. Start the sign-in again.";
|
|
161
|
+
case "token-exchange-rejected":
|
|
162
|
+
return `OpenAI refused the sign-in token exchange (HTTP ${String(failure.status)}).`;
|
|
163
|
+
case "cancelled":
|
|
164
|
+
return "Authentication cancelled";
|
|
165
|
+
case "no-key-entered":
|
|
166
|
+
return new LocalAuthRefusal().message;
|
|
167
|
+
case "unknown":
|
|
168
|
+
return "Local authentication failed";
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
/** The client-side error for one typed failure; its message is fixed text. */
|
|
172
|
+
class LocalAuthFailure extends Error {
|
|
173
|
+
failure;
|
|
174
|
+
constructor(failure) {
|
|
175
|
+
super(failureText(failure));
|
|
176
|
+
this.failure = failure;
|
|
177
|
+
this.name = "LocalAuthFailure";
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
class AuthPromptTarget extends RpcTarget {
|
|
181
|
+
#controller = new AbortController();
|
|
182
|
+
#result;
|
|
183
|
+
constructor(prompt, interaction) {
|
|
184
|
+
super();
|
|
185
|
+
const signal = interaction.signal === undefined
|
|
186
|
+
? this.#controller.signal
|
|
187
|
+
: AbortSignal.any([this.#controller.signal, interaction.signal]);
|
|
188
|
+
this.#result = Promise.resolve()
|
|
189
|
+
.then(() => {
|
|
190
|
+
signal.throwIfAborted();
|
|
191
|
+
const request = prompt.type === "select"
|
|
192
|
+
? {
|
|
193
|
+
type: "select",
|
|
194
|
+
message: prompt.message,
|
|
195
|
+
signal,
|
|
196
|
+
options: prompt.options.map((option) => option.description === undefined
|
|
197
|
+
? { id: option.id, label: option.label }
|
|
198
|
+
: {
|
|
199
|
+
id: option.id,
|
|
200
|
+
label: option.label,
|
|
201
|
+
description: option.description,
|
|
202
|
+
}),
|
|
203
|
+
}
|
|
204
|
+
: { type: prompt.type, message: prompt.message, signal };
|
|
205
|
+
if (request.type !== "select" &&
|
|
206
|
+
prompt.type !== "select" &&
|
|
207
|
+
prompt.placeholder !== undefined)
|
|
208
|
+
request.placeholder = prompt.placeholder;
|
|
209
|
+
return interaction.prompt(request);
|
|
210
|
+
})
|
|
211
|
+
.then((value) => ({ value: answerSchema.parse(value) }), (cause) => ({ failed: true, cause }));
|
|
212
|
+
}
|
|
213
|
+
async result() {
|
|
214
|
+
const result = await this.#result;
|
|
215
|
+
if ("failed" in result)
|
|
216
|
+
throw new Error("Authentication prompt cancelled or failed", {
|
|
217
|
+
cause: result.cause,
|
|
218
|
+
});
|
|
219
|
+
return result.value;
|
|
220
|
+
}
|
|
221
|
+
cancel() {
|
|
222
|
+
this.#controller.abort();
|
|
223
|
+
}
|
|
224
|
+
[Symbol.dispose]() {
|
|
225
|
+
this.cancel();
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
class AuthPresenterTarget extends RpcTarget {
|
|
229
|
+
#interaction;
|
|
230
|
+
constructor(interaction) {
|
|
231
|
+
super();
|
|
232
|
+
this.#interaction = interaction;
|
|
233
|
+
}
|
|
234
|
+
openPrompt(request) {
|
|
235
|
+
return new AuthPromptTarget(promptSchema.parse(request), this.#interaction);
|
|
236
|
+
}
|
|
237
|
+
notify(event) {
|
|
238
|
+
const parsed = eventSchema.parse(event);
|
|
239
|
+
let decoded;
|
|
240
|
+
switch (parsed.type) {
|
|
241
|
+
case "info":
|
|
242
|
+
decoded = { type: parsed.type, message: parsed.message };
|
|
243
|
+
if (parsed.links !== undefined)
|
|
244
|
+
decoded.links = parsed.links.map((link) => link.label === undefined
|
|
245
|
+
? { url: link.url }
|
|
246
|
+
: { url: link.url, label: link.label });
|
|
247
|
+
break;
|
|
248
|
+
case "progress":
|
|
249
|
+
decoded = parsed;
|
|
250
|
+
break;
|
|
251
|
+
case "auth_url":
|
|
252
|
+
decoded = { type: parsed.type, url: parsed.url };
|
|
253
|
+
if (parsed.instructions !== undefined)
|
|
254
|
+
decoded.instructions = parsed.instructions;
|
|
255
|
+
break;
|
|
256
|
+
case "device_code":
|
|
257
|
+
decoded = {
|
|
258
|
+
type: parsed.type,
|
|
259
|
+
userCode: parsed.userCode,
|
|
260
|
+
verificationUri: parsed.verificationUri,
|
|
261
|
+
};
|
|
262
|
+
if (parsed.intervalSeconds !== undefined)
|
|
263
|
+
decoded.intervalSeconds = parsed.intervalSeconds;
|
|
264
|
+
if (parsed.expiresInSeconds !== undefined)
|
|
265
|
+
decoded.expiresInSeconds = parsed.expiresInSeconds;
|
|
266
|
+
break;
|
|
267
|
+
}
|
|
268
|
+
this.#interaction.notify(decoded);
|
|
269
|
+
}
|
|
270
|
+
}
|
|
271
|
+
/** One transient connection runs one provider or Fungi-account operation. */
|
|
272
|
+
export class PrivateLocalAuthTarget extends RpcTarget {
|
|
273
|
+
#controller = new AbortController();
|
|
274
|
+
#used = false;
|
|
275
|
+
#run;
|
|
276
|
+
#signOutAccount;
|
|
277
|
+
constructor(run, signOutAccount) {
|
|
278
|
+
super();
|
|
279
|
+
this.#run = run;
|
|
280
|
+
this.#signOutAccount = signOutAccount;
|
|
281
|
+
}
|
|
282
|
+
async signOutFungi(input) {
|
|
283
|
+
if (this.#used)
|
|
284
|
+
throw new Error("Authentication connection already used");
|
|
285
|
+
this.#used = true;
|
|
286
|
+
return fungiAccountLogoutResultSchema.parse(await this.#signOutAccount(fungiAccountLogoutRequestSchema.parse(input)));
|
|
287
|
+
}
|
|
288
|
+
cancel() {
|
|
289
|
+
this.#controller.abort();
|
|
290
|
+
}
|
|
291
|
+
[Symbol.dispose]() {
|
|
292
|
+
this.cancel();
|
|
293
|
+
}
|
|
294
|
+
async run(input, presenter) {
|
|
295
|
+
if (this.#used)
|
|
296
|
+
throw new Error("Authentication connection already used");
|
|
297
|
+
this.#used = true;
|
|
298
|
+
presenter.onRpcBroken(() => {
|
|
299
|
+
this.cancel();
|
|
300
|
+
});
|
|
301
|
+
const request = requestSchema.parse(input);
|
|
302
|
+
const args = [request.command];
|
|
303
|
+
if (request.provider !== undefined)
|
|
304
|
+
args.push("--provider", request.provider);
|
|
305
|
+
if (request.authType !== undefined)
|
|
306
|
+
args.push("--auth-type", request.authType);
|
|
307
|
+
if (request.method !== undefined)
|
|
308
|
+
args.push("--method", request.method);
|
|
309
|
+
const signal = this.#controller.signal;
|
|
310
|
+
try {
|
|
311
|
+
const result = await this.#run(args, {
|
|
312
|
+
interaction: {
|
|
313
|
+
signal,
|
|
314
|
+
notify: (event) => {
|
|
315
|
+
void presenter.notify(event).catch((cause) => {
|
|
316
|
+
console.warn("Local authentication presenter notification failed", cause instanceof Error ? cause.name : "NonError");
|
|
317
|
+
this.cancel();
|
|
318
|
+
});
|
|
319
|
+
},
|
|
320
|
+
prompt: async (request) => {
|
|
321
|
+
const promptSignal = request.signal === undefined
|
|
322
|
+
? signal
|
|
323
|
+
: AbortSignal.any([signal, request.signal]);
|
|
324
|
+
promptSignal.throwIfAborted();
|
|
325
|
+
const pending = presenter.openPrompt(promptSchema.parse(request));
|
|
326
|
+
const cancel = () => {
|
|
327
|
+
void pending.cancel().catch((cause) => {
|
|
328
|
+
console.warn("Local authentication prompt cancellation failed", cause instanceof Error ? cause.name : "NonError");
|
|
329
|
+
this.cancel();
|
|
330
|
+
});
|
|
331
|
+
};
|
|
332
|
+
promptSignal.addEventListener("abort", cancel, { once: true });
|
|
333
|
+
try {
|
|
334
|
+
if (promptSignal.aborted)
|
|
335
|
+
cancel();
|
|
336
|
+
return answerSchema.parse(await pending.result());
|
|
337
|
+
}
|
|
338
|
+
finally {
|
|
339
|
+
promptSignal.removeEventListener("abort", cancel);
|
|
340
|
+
pending[Symbol.dispose]();
|
|
341
|
+
}
|
|
342
|
+
},
|
|
343
|
+
},
|
|
344
|
+
});
|
|
345
|
+
return authResultSchema.parse(result);
|
|
346
|
+
}
|
|
347
|
+
catch (cause) {
|
|
348
|
+
return {
|
|
349
|
+
kind: "failed",
|
|
350
|
+
failure: classifyFailure(cause, signal.aborted),
|
|
351
|
+
};
|
|
352
|
+
}
|
|
353
|
+
finally {
|
|
354
|
+
this.cancel();
|
|
355
|
+
}
|
|
356
|
+
}
|
|
357
|
+
}
|
|
358
|
+
/**
|
|
359
|
+
* Takes ownership of a dedicated auth RPC connection, never the Session client.
|
|
360
|
+
* A cancel the caller asked for is awaited before the connection is released,
|
|
361
|
+
* so an ordinary cancel is never cut off by its own teardown; a cancel that
|
|
362
|
+
* genuinely fails is handed to `reportCleanupFailure` with its cause.
|
|
363
|
+
*/
|
|
364
|
+
export async function runPrivateLocalAuthRemote(remote, request, interaction, reportCleanupFailure) {
|
|
365
|
+
const controller = new AbortController();
|
|
366
|
+
const signal = interaction.signal === undefined
|
|
367
|
+
? controller.signal
|
|
368
|
+
: AbortSignal.any([controller.signal, interaction.signal]);
|
|
369
|
+
const presenter = new AuthPresenterTarget({ ...interaction, signal });
|
|
370
|
+
let cancellation;
|
|
371
|
+
const cancel = () => {
|
|
372
|
+
cancellation = (async () => {
|
|
373
|
+
await remote.cancel();
|
|
374
|
+
})();
|
|
375
|
+
};
|
|
376
|
+
signal.addEventListener("abort", cancel, { once: true });
|
|
377
|
+
try {
|
|
378
|
+
signal.throwIfAborted();
|
|
379
|
+
const result = wireResultSchema.parse(await remote.run(request, presenter));
|
|
380
|
+
if (result.kind === "failed")
|
|
381
|
+
throw new LocalAuthFailure(result.failure);
|
|
382
|
+
return result;
|
|
383
|
+
}
|
|
384
|
+
finally {
|
|
385
|
+
signal.removeEventListener("abort", cancel);
|
|
386
|
+
controller.abort();
|
|
387
|
+
try {
|
|
388
|
+
const [cancelled] = await Promise.allSettled([cancellation]);
|
|
389
|
+
if (cancelled.status === "rejected")
|
|
390
|
+
reportCleanupFailure(cancelled.reason);
|
|
391
|
+
}
|
|
392
|
+
finally {
|
|
393
|
+
remote[Symbol.dispose]();
|
|
394
|
+
}
|
|
395
|
+
}
|
|
396
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
export declare class LocalBwrapUnavailable extends Error {
|
|
2
|
+
readonly reason: "missing" | "invalid-digest" | "digest-mismatch" | "not-executable";
|
|
3
|
+
readonly name = "LocalBwrapUnavailable";
|
|
4
|
+
constructor(reason: "missing" | "invalid-digest" | "digest-mismatch" | "not-executable", message: string, options?: ErrorOptions);
|
|
5
|
+
}
|
|
6
|
+
export type LinuxSandboxPayload = {
|
|
7
|
+
readonly executable: string;
|
|
8
|
+
readonly seccompFilter: Uint8Array;
|
|
9
|
+
};
|
|
10
|
+
/** Check both packaged artifacts on every use; never cache verification. */
|
|
11
|
+
export declare function verifyLocalLinuxSandboxPayload(executable: string): LinuxSandboxPayload;
|
|
12
|
+
/** Same optional native-package lookup as apply_patch, with no PATH fallback. */
|
|
13
|
+
export declare function localLinuxSandboxPayload(): LinuxSandboxPayload;
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
import { createHash } from "node:crypto";
|
|
2
|
+
import { accessSync, constants, readFileSync, statSync } from "node:fs";
|
|
3
|
+
import { createRequire } from "node:module";
|
|
4
|
+
import { dirname, join } from "node:path";
|
|
5
|
+
import { fileURLToPath } from "node:url";
|
|
6
|
+
import { z } from "zod";
|
|
7
|
+
const digestSchema = z
|
|
8
|
+
.string()
|
|
9
|
+
.trim()
|
|
10
|
+
.regex(/^[a-f0-9]{64}$/u);
|
|
11
|
+
export class LocalBwrapUnavailable extends Error {
|
|
12
|
+
reason;
|
|
13
|
+
name = "LocalBwrapUnavailable";
|
|
14
|
+
constructor(reason, message, options) {
|
|
15
|
+
super(message, options);
|
|
16
|
+
this.reason = reason;
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
/** Check both packaged artifacts on every use; never cache verification. */
|
|
20
|
+
export function verifyLocalLinuxSandboxPayload(executable) {
|
|
21
|
+
verifiedBytes(executable, "fungi-bwrap");
|
|
22
|
+
try {
|
|
23
|
+
accessSync(executable, constants.X_OK);
|
|
24
|
+
}
|
|
25
|
+
catch (cause) {
|
|
26
|
+
throw new LocalBwrapUnavailable("not-executable", "Packaged fungi-bwrap cannot be executed; reinstall Fungi.", { cause });
|
|
27
|
+
}
|
|
28
|
+
return {
|
|
29
|
+
executable,
|
|
30
|
+
seccompFilter: verifiedBytes(join(dirname(executable), "fungi-seccomp.bpf"), "fungi-seccomp.bpf"),
|
|
31
|
+
};
|
|
32
|
+
}
|
|
33
|
+
function verifiedBytes(path, label) {
|
|
34
|
+
let bytes;
|
|
35
|
+
let digest;
|
|
36
|
+
try {
|
|
37
|
+
if (!statSync(path).isFile())
|
|
38
|
+
throw new LocalBwrapUnavailable("not-executable", `Packaged ${label} is not a regular file; reinstall Fungi.`);
|
|
39
|
+
bytes = readFileSync(path);
|
|
40
|
+
digest = readFileSync(`${path}.sha256`, "utf8");
|
|
41
|
+
}
|
|
42
|
+
catch (cause) {
|
|
43
|
+
if (cause instanceof Error && "code" in cause && cause.code === "ENOENT")
|
|
44
|
+
throw new LocalBwrapUnavailable("missing", `Packaged ${label} or its digest is missing; reinstall Fungi with optional dependencies enabled.`, { cause });
|
|
45
|
+
throw cause;
|
|
46
|
+
}
|
|
47
|
+
const expected = digestSchema.safeParse(digest);
|
|
48
|
+
if (!expected.success)
|
|
49
|
+
throw new LocalBwrapUnavailable("invalid-digest", `Packaged ${label} has an invalid SHA-256 record; reinstall Fungi.`);
|
|
50
|
+
if (createHash("sha256").update(bytes).digest("hex") !== expected.data)
|
|
51
|
+
throw new LocalBwrapUnavailable("digest-mismatch", `Packaged ${label} digest mismatch; reinstall Fungi. No command was started.`);
|
|
52
|
+
return bytes;
|
|
53
|
+
}
|
|
54
|
+
/** Same optional native-package lookup as apply_patch, with no PATH fallback. */
|
|
55
|
+
export function localLinuxSandboxPayload() {
|
|
56
|
+
let executable;
|
|
57
|
+
if (process.env.FUNGI_BUNDLED_CLI) {
|
|
58
|
+
const name = `@fungi.computer/spore-native-${process.platform}-${process.arch}`;
|
|
59
|
+
try {
|
|
60
|
+
executable = createRequire(process.env.FUNGI_BUNDLED_CLI).resolve(`${name}/bin/fungi-bwrap`);
|
|
61
|
+
}
|
|
62
|
+
catch (cause) {
|
|
63
|
+
throw new LocalBwrapUnavailable("missing", `Missing ${name}/bin/fungi-bwrap; reinstall Fungi with optional dependencies enabled.`, { cause });
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
else {
|
|
67
|
+
executable = fileURLToPath(new URL("../native/bin/fungi-bwrap", import.meta.url));
|
|
68
|
+
}
|
|
69
|
+
return verifyLocalLinuxSandboxPayload(executable);
|
|
70
|
+
}
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import { execFile } from "node:child_process";
|
|
2
|
+
import { createHash } from "node:crypto";
|
|
3
|
+
import { readFile, realpath } from "node:fs/promises";
|
|
4
|
+
import { basename, dirname, join } from "node:path";
|
|
5
|
+
import { promisify } from "node:util";
|
|
6
|
+
import { z } from "zod";
|
|
7
|
+
const execute = promisify(execFile);
|
|
8
|
+
const queryOptions = { timeout: 2_000, maxBuffer: 4_096 };
|
|
9
|
+
const buildId = z.string().regex(/^[a-f0-9]{64}$/u);
|
|
10
|
+
function installedLauncherOnPath() {
|
|
11
|
+
return new Promise((resolve, reject) => {
|
|
12
|
+
execFile("which", ["fungi"], queryOptions, (error, stdout) => {
|
|
13
|
+
if (error === null)
|
|
14
|
+
resolve(stdout.trim());
|
|
15
|
+
// which exits 1 when nothing named fungi is installed on PATH.
|
|
16
|
+
else if (error.code === 1)
|
|
17
|
+
resolve(undefined);
|
|
18
|
+
else
|
|
19
|
+
reject(new Error("Could not look up the installed fungi launcher", {
|
|
20
|
+
cause: error,
|
|
21
|
+
}));
|
|
22
|
+
});
|
|
23
|
+
});
|
|
24
|
+
}
|
|
25
|
+
/**
|
|
26
|
+
* Read the installed producer, never the calling window's frozen environment.
|
|
27
|
+
* Undefined means no fungi is installed on PATH, so no client is current.
|
|
28
|
+
*/
|
|
29
|
+
export async function readInstalledLocalClientBuildId(launcher) {
|
|
30
|
+
const found = launcher ?? (await installedLauncherOnPath());
|
|
31
|
+
if (found === undefined)
|
|
32
|
+
return undefined;
|
|
33
|
+
const installed = await realpath(found);
|
|
34
|
+
if (basename(installed) === "fungi.cjs") {
|
|
35
|
+
// npm replaces cli.js in place. This is the launcher's own identity rule.
|
|
36
|
+
return createHash("sha256")
|
|
37
|
+
.update(await readFile(join(dirname(installed), "cli.js")))
|
|
38
|
+
.digest("hex");
|
|
39
|
+
}
|
|
40
|
+
// Compiled launchers identify their embedded archive, not executable bytes.
|
|
41
|
+
const { stdout } = await execute(installed, ["--internal-build-id"], queryOptions);
|
|
42
|
+
return buildId.parse(JSON.parse(stdout));
|
|
43
|
+
}
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import type { FetchLike } from "@modelcontextprotocol/client";
|
|
2
|
+
import { type LocalBackendStatus } from "./private-local-computer-backend.js";
|
|
3
|
+
export type ControlOptions = Readonly<{
|
|
4
|
+
credentialPath?: string;
|
|
5
|
+
fetch?: FetchLike;
|
|
6
|
+
}>;
|
|
7
|
+
export type PrivateLocalComputerAttachResult = Readonly<{
|
|
8
|
+
kind: "attached";
|
|
9
|
+
computerId: string;
|
|
10
|
+
computerName: string;
|
|
11
|
+
backendId: string;
|
|
12
|
+
rootPath: string;
|
|
13
|
+
}> | Readonly<{
|
|
14
|
+
kind: "not-found" | "uncertain" | "denied" | "conflict" | "invalid";
|
|
15
|
+
}>;
|
|
16
|
+
/** Attach `folderPath` to a Team Computer, or return the existing attachment. */
|
|
17
|
+
export declare function attachPrivateLocalComputer(rawEndpoint: string | URL, input: Readonly<{
|
|
18
|
+
computer: string;
|
|
19
|
+
folderPath: string;
|
|
20
|
+
hostLabel?: string;
|
|
21
|
+
}>, controlOptions?: ControlOptions): Promise<PrivateLocalComputerAttachResult>;
|
|
22
|
+
export type PrivateLocalComputerDetachResult = Readonly<{
|
|
23
|
+
kind: "detached" | "not-attached" | "uncertain" | "denied";
|
|
24
|
+
}>;
|
|
25
|
+
/**
|
|
26
|
+
* Detach a Computer's attachment on the Hub, then forget it here. A pending
|
|
27
|
+
* attach is replayed first so the Hub never keeps an attachment Local forgot.
|
|
28
|
+
*/
|
|
29
|
+
export declare function detachPrivateLocalComputer(rawEndpoint: string | URL, computer: string, input?: ControlOptions): Promise<PrivateLocalComputerDetachResult>;
|
|
30
|
+
/** What `fungi computer status` shows: never a key. */
|
|
31
|
+
export declare function listPrivateLocalComputers(options?: Readonly<{
|
|
32
|
+
credentialPath?: string;
|
|
33
|
+
}>): Promise<readonly Readonly<{
|
|
34
|
+
teamId: string;
|
|
35
|
+
computerId: string;
|
|
36
|
+
computerName: string;
|
|
37
|
+
rootPath: string;
|
|
38
|
+
backendId: string | null;
|
|
39
|
+
}>[]>;
|
|
40
|
+
export type PrivateLocalComputerRunOptions = {
|
|
41
|
+
credentialPath?: string;
|
|
42
|
+
signal: AbortSignal;
|
|
43
|
+
onStatus?: (status: LocalBackendStatus) => void;
|
|
44
|
+
};
|
|
45
|
+
/**
|
|
46
|
+
* Serve one attached folder until `signal` aborts or the Hub refuses the
|
|
47
|
+
* machine (it was detached elsewhere), in which case Local forgets it.
|
|
48
|
+
*/
|
|
49
|
+
export declare function runPrivateLocalComputer(rawEndpoint: string | URL, computer: string, input: PrivateLocalComputerRunOptions): Promise<"stopped" | "detached">;
|