@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,172 @@
|
|
|
1
|
+
import type { Approvals, PendingApproval } from "@fungi.computer/approvals";
|
|
2
|
+
import type { ShiitakeSession } from "@fungi.computer/shiitake";
|
|
3
|
+
import { type LocalUiAttachment } from "./private-local-ui-attachment.js";
|
|
4
|
+
/** Host-owned references to canonical approvals; no suspended tool or second ledger. */
|
|
5
|
+
export declare function createMcpFormSession(): {
|
|
6
|
+
extension: import("@fungi.computer/shiitake/extensions").DefinedExtensionV0<{
|
|
7
|
+
answer: {
|
|
8
|
+
scope: "session";
|
|
9
|
+
input: import("effect/Schema").Struct<{
|
|
10
|
+
readonly id: import("effect/Schema").String;
|
|
11
|
+
readonly decision: import("effect/Schema").Literals<readonly ["approve", "deny"]>;
|
|
12
|
+
readonly expectedRevision: import("effect/Schema").Int;
|
|
13
|
+
}>;
|
|
14
|
+
output: import("effect/Schema").Union<readonly [import("effect/Schema").Struct<{
|
|
15
|
+
readonly kind: import("effect/Schema").Literal<"answered">;
|
|
16
|
+
readonly view: import("effect/Schema").Struct<{
|
|
17
|
+
readonly record: import("effect/Schema").Struct<{
|
|
18
|
+
readonly recordId: import("effect/Schema").String;
|
|
19
|
+
readonly requester: import("effect/Schema").Struct<{
|
|
20
|
+
readonly principalId: import("effect/Schema").String;
|
|
21
|
+
readonly agentId: import("effect/Schema").NullOr<import("effect/Schema").String>;
|
|
22
|
+
readonly sessionId: import("effect/Schema").NullOr<import("effect/Schema").String>;
|
|
23
|
+
readonly displayName: import("effect/Schema").String;
|
|
24
|
+
}>;
|
|
25
|
+
readonly module: import("effect/Schema").String;
|
|
26
|
+
readonly operation: import("effect/Schema").String;
|
|
27
|
+
readonly access: import("effect/Schema").Literals<readonly ["write", "external"]>;
|
|
28
|
+
readonly description: import("effect/Schema").Struct<{
|
|
29
|
+
readonly title: import("effect/Schema").String;
|
|
30
|
+
readonly inputBytes: import("effect/Schema").Int;
|
|
31
|
+
readonly inputSha256: import("effect/Schema").String;
|
|
32
|
+
}>;
|
|
33
|
+
readonly evidence: import("effect/Schema").optionalKey<import("effect/Schema").$Array<import("effect/Schema").Struct<{
|
|
34
|
+
readonly kind: import("effect/Schema").Literal<"artifact">;
|
|
35
|
+
readonly ref: import("effect/Schema").String;
|
|
36
|
+
readonly mediaType: import("effect/Schema").Literal<"image/png">;
|
|
37
|
+
readonly bytes: import("effect/Schema").Int;
|
|
38
|
+
}>>>;
|
|
39
|
+
readonly authorityFence: import("effect/Schema").String;
|
|
40
|
+
readonly reversibility: import("effect/Schema").Literals<readonly ["reversible", "irreversible-external"]>;
|
|
41
|
+
readonly state: import("effect/Schema").Literals<readonly ["requested", "pending", "approved", "claimed", "applied", "denied", "expired", "outcome_unknown"]>;
|
|
42
|
+
readonly by: import("effect/Schema").NullOr<import("effect/Schema").Union<readonly [import("effect/Schema").Struct<{
|
|
43
|
+
readonly kind: import("effect/Schema").Literal<"human">;
|
|
44
|
+
readonly memberId: import("effect/Schema").String;
|
|
45
|
+
}>, import("effect/Schema").Struct<{
|
|
46
|
+
readonly kind: import("effect/Schema").Literal<"policy">;
|
|
47
|
+
}>]>>;
|
|
48
|
+
readonly reason: import("effect/Schema").NullOr<import("effect/Schema").Literals<readonly ["not-found", "not-eligible", "already-answered", "stale-revision", "expired", "declined", "fenced", "not-granted", "read-only", "pending", "in-flight", "outcome-unknown", "identity-conflict"]>>;
|
|
49
|
+
readonly requestedAt: import("effect/Schema").Int;
|
|
50
|
+
readonly expiresAt: import("effect/Schema").Int;
|
|
51
|
+
readonly updatedAt: import("effect/Schema").Int;
|
|
52
|
+
readonly revision: import("effect/Schema").Int;
|
|
53
|
+
}>;
|
|
54
|
+
readonly canAnswer: import("effect/Schema").Boolean;
|
|
55
|
+
readonly canSetAutoApprove: import("effect/Schema").Boolean;
|
|
56
|
+
}>;
|
|
57
|
+
}>, import("effect/Schema").Struct<{
|
|
58
|
+
readonly kind: import("effect/Schema").Literal<"refused">;
|
|
59
|
+
readonly reason: import("effect/Schema").Literals<readonly ["not-found", "not-eligible", "already-answered", "stale-revision", "expired", "declined", "fenced", "not-granted", "read-only", "pending", "in-flight", "outcome-unknown", "identity-conflict"]>;
|
|
60
|
+
}>, import("effect/Schema").Struct<{
|
|
61
|
+
readonly kind: import("effect/Schema").Literal<"submitted">;
|
|
62
|
+
}>, import("effect/Schema").Struct<{
|
|
63
|
+
readonly kind: import("effect/Schema").Literal<"refused">;
|
|
64
|
+
readonly reason: import("effect/Schema").Literals<readonly ["not-attached", "not-pending"]>;
|
|
65
|
+
}>]>;
|
|
66
|
+
};
|
|
67
|
+
}, {
|
|
68
|
+
readonly pending: readonly {
|
|
69
|
+
readonly id: string;
|
|
70
|
+
readonly approval: {
|
|
71
|
+
readonly record: {
|
|
72
|
+
readonly operation: string;
|
|
73
|
+
readonly state: "pending" | "expired" | "requested" | "approved" | "claimed" | "applied" | "denied" | "outcome_unknown";
|
|
74
|
+
readonly access: "write" | "external";
|
|
75
|
+
readonly reason: "pending" | "not-found" | "not-eligible" | "already-answered" | "stale-revision" | "expired" | "declined" | "fenced" | "not-granted" | "read-only" | "in-flight" | "outcome-unknown" | "identity-conflict" | null;
|
|
76
|
+
readonly recordId: string;
|
|
77
|
+
readonly requester: {
|
|
78
|
+
readonly principalId: string;
|
|
79
|
+
readonly agentId: string | null;
|
|
80
|
+
readonly sessionId: string | null;
|
|
81
|
+
readonly displayName: string;
|
|
82
|
+
};
|
|
83
|
+
readonly module: string;
|
|
84
|
+
readonly description: {
|
|
85
|
+
readonly title: string;
|
|
86
|
+
readonly inputBytes: number;
|
|
87
|
+
readonly inputSha256: string;
|
|
88
|
+
};
|
|
89
|
+
readonly authorityFence: string;
|
|
90
|
+
readonly reversibility: "reversible" | "irreversible-external";
|
|
91
|
+
readonly by: {
|
|
92
|
+
readonly kind: "human";
|
|
93
|
+
readonly memberId: string;
|
|
94
|
+
} | {
|
|
95
|
+
readonly kind: "policy";
|
|
96
|
+
} | null;
|
|
97
|
+
readonly requestedAt: number;
|
|
98
|
+
readonly expiresAt: number;
|
|
99
|
+
readonly updatedAt: number;
|
|
100
|
+
readonly revision: number;
|
|
101
|
+
readonly evidence?: readonly {
|
|
102
|
+
readonly kind: "artifact";
|
|
103
|
+
readonly ref: string;
|
|
104
|
+
readonly mediaType: "image/png";
|
|
105
|
+
readonly bytes: number;
|
|
106
|
+
}[];
|
|
107
|
+
};
|
|
108
|
+
readonly canAnswer: boolean;
|
|
109
|
+
readonly canSetAutoApprove: boolean;
|
|
110
|
+
};
|
|
111
|
+
}[];
|
|
112
|
+
}, {
|
|
113
|
+
readonly pending: readonly {
|
|
114
|
+
readonly id: string;
|
|
115
|
+
readonly approval: {
|
|
116
|
+
readonly record: {
|
|
117
|
+
readonly operation: string;
|
|
118
|
+
readonly state: "pending" | "expired" | "requested" | "approved" | "claimed" | "applied" | "denied" | "outcome_unknown";
|
|
119
|
+
readonly access: "write" | "external";
|
|
120
|
+
readonly reason: "pending" | "not-found" | "not-eligible" | "already-answered" | "stale-revision" | "expired" | "declined" | "fenced" | "not-granted" | "read-only" | "in-flight" | "outcome-unknown" | "identity-conflict" | null;
|
|
121
|
+
readonly recordId: string;
|
|
122
|
+
readonly requester: {
|
|
123
|
+
readonly principalId: string;
|
|
124
|
+
readonly agentId: string | null;
|
|
125
|
+
readonly sessionId: string | null;
|
|
126
|
+
readonly displayName: string;
|
|
127
|
+
};
|
|
128
|
+
readonly module: string;
|
|
129
|
+
readonly description: {
|
|
130
|
+
readonly title: string;
|
|
131
|
+
readonly inputBytes: number;
|
|
132
|
+
readonly inputSha256: string;
|
|
133
|
+
};
|
|
134
|
+
readonly authorityFence: string;
|
|
135
|
+
readonly reversibility: "reversible" | "irreversible-external";
|
|
136
|
+
readonly by: {
|
|
137
|
+
readonly kind: "human";
|
|
138
|
+
readonly memberId: string;
|
|
139
|
+
} | {
|
|
140
|
+
readonly kind: "policy";
|
|
141
|
+
} | null;
|
|
142
|
+
readonly requestedAt: number;
|
|
143
|
+
readonly expiresAt: number;
|
|
144
|
+
readonly updatedAt: number;
|
|
145
|
+
readonly revision: number;
|
|
146
|
+
readonly evidence?: readonly {
|
|
147
|
+
readonly kind: "artifact";
|
|
148
|
+
readonly ref: string;
|
|
149
|
+
readonly mediaType: "image/png";
|
|
150
|
+
readonly bytes: number;
|
|
151
|
+
}[];
|
|
152
|
+
};
|
|
153
|
+
readonly canAnswer: boolean;
|
|
154
|
+
readonly canSetAutoApprove: boolean;
|
|
155
|
+
};
|
|
156
|
+
}[];
|
|
157
|
+
}>;
|
|
158
|
+
presentApproval: (sessionId: string, submitted: PendingApproval, approvals: Approvals) => Promise<{
|
|
159
|
+
readonly kind: "refused";
|
|
160
|
+
readonly reason: "not-pending";
|
|
161
|
+
readonly id?: never;
|
|
162
|
+
} | {
|
|
163
|
+
readonly kind: "presented";
|
|
164
|
+
readonly id: string;
|
|
165
|
+
reason?: never;
|
|
166
|
+
} | {
|
|
167
|
+
readonly kind: "refused";
|
|
168
|
+
readonly reason: "capacity";
|
|
169
|
+
readonly id?: never;
|
|
170
|
+
}>;
|
|
171
|
+
bindSession: (sessionId: string, session: ShiitakeSession, attachment: LocalUiAttachment | undefined) => ShiitakeSession;
|
|
172
|
+
};
|
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
import { correlationKey } from "@fungi.computer/cairn";
|
|
2
|
+
import { defineExtension } from "@fungi.computer/shiitake/extensions";
|
|
3
|
+
import * as Effect from "effect/Effect";
|
|
4
|
+
import { mcpFormApprovalContract, } from "./private-mcp-form-contract.js";
|
|
5
|
+
import { consumeLocalUiCommand, runLocalUiCommand, startLocalUiWatch, } from "./private-local-ui-attachment.js";
|
|
6
|
+
const maximumPendingForms = 100;
|
|
7
|
+
/** Host-owned references to canonical approvals; no suspended tool or second ledger. */
|
|
8
|
+
export function createMcpFormSession() {
|
|
9
|
+
const pending = new Map();
|
|
10
|
+
let publish;
|
|
11
|
+
const extension = defineExtension({
|
|
12
|
+
id: mcpFormApprovalContract.id,
|
|
13
|
+
version: "1.0.0",
|
|
14
|
+
}).define({
|
|
15
|
+
commands: {
|
|
16
|
+
specs: mcpFormApprovalContract.commands,
|
|
17
|
+
handlers: (context) => ({
|
|
18
|
+
answer: ({ sessionId, input }) => Effect.gen(function* () {
|
|
19
|
+
const onComplete = consumeLocalUiCommand(sessionId);
|
|
20
|
+
if (onComplete === undefined) {
|
|
21
|
+
const result = {
|
|
22
|
+
kind: "refused",
|
|
23
|
+
reason: "not-attached",
|
|
24
|
+
};
|
|
25
|
+
return result;
|
|
26
|
+
}
|
|
27
|
+
const entry = pending.get(input.id);
|
|
28
|
+
if (entry?.sessionId !== sessionId) {
|
|
29
|
+
const result = {
|
|
30
|
+
kind: "refused",
|
|
31
|
+
reason: "not-pending",
|
|
32
|
+
};
|
|
33
|
+
return result;
|
|
34
|
+
}
|
|
35
|
+
if (entry.answering) {
|
|
36
|
+
const result = {
|
|
37
|
+
kind: "refused",
|
|
38
|
+
reason: "in-flight",
|
|
39
|
+
};
|
|
40
|
+
return result;
|
|
41
|
+
}
|
|
42
|
+
entry.answering = true;
|
|
43
|
+
onComplete(async (committed) => {
|
|
44
|
+
try {
|
|
45
|
+
if (!committed)
|
|
46
|
+
return { kind: "refused", reason: "not-attached" };
|
|
47
|
+
// Session commit and live UI recheck precede the canonical
|
|
48
|
+
// decision. Its bound host, never MCP accept, owns apply.
|
|
49
|
+
return await entry.approvals.answer({
|
|
50
|
+
recordId: entry.approval.record.recordId,
|
|
51
|
+
decision: input.decision,
|
|
52
|
+
expectedRevision: input.expectedRevision,
|
|
53
|
+
});
|
|
54
|
+
}
|
|
55
|
+
finally {
|
|
56
|
+
entry.answering = false;
|
|
57
|
+
const view = await entry.approvals.get({
|
|
58
|
+
recordId: entry.approval.record.recordId,
|
|
59
|
+
});
|
|
60
|
+
if (view?.record.state !== "pending")
|
|
61
|
+
pending.delete(input.id);
|
|
62
|
+
else
|
|
63
|
+
entry.approval = view;
|
|
64
|
+
await publish?.(sessionId);
|
|
65
|
+
}
|
|
66
|
+
});
|
|
67
|
+
yield* context.observe(sessionId);
|
|
68
|
+
const result = { kind: "submitted" };
|
|
69
|
+
return result;
|
|
70
|
+
}),
|
|
71
|
+
}),
|
|
72
|
+
},
|
|
73
|
+
hooks: {
|
|
74
|
+
start: (context) => {
|
|
75
|
+
publish = (sessionId) => Effect.runPromise(context.transact(context.observe(sessionId)));
|
|
76
|
+
},
|
|
77
|
+
},
|
|
78
|
+
projection: {
|
|
79
|
+
schema: mcpFormApprovalContract.projection.schema,
|
|
80
|
+
read: (_context, sessionId) => ({
|
|
81
|
+
pending: [...pending.entries()]
|
|
82
|
+
.filter(([, entry]) => entry.sessionId === sessionId)
|
|
83
|
+
.map(([id, entry]) => ({ id, approval: entry.approval })),
|
|
84
|
+
}),
|
|
85
|
+
},
|
|
86
|
+
});
|
|
87
|
+
/**
|
|
88
|
+
* Native host port, never a model or UI command. The caller supplies the
|
|
89
|
+
* actual submit result and a member-bound capability, not form JSON.
|
|
90
|
+
*/
|
|
91
|
+
async function presentApproval(sessionId, submitted, approvals) {
|
|
92
|
+
const approval = await approvals.get({ recordId: submitted.recordId });
|
|
93
|
+
if (approval?.record.state !== "pending" ||
|
|
94
|
+
approval.record.requester.sessionId !== sessionId)
|
|
95
|
+
return { kind: "refused", reason: "not-pending" };
|
|
96
|
+
const existing = [...pending.entries()].find(([, entry]) => entry.sessionId === sessionId &&
|
|
97
|
+
entry.approval.record.recordId === submitted.recordId);
|
|
98
|
+
if (existing !== undefined) {
|
|
99
|
+
const [id, entry] = existing;
|
|
100
|
+
entry.approval = approval;
|
|
101
|
+
entry.approvals = approvals;
|
|
102
|
+
await publish?.(sessionId);
|
|
103
|
+
return { kind: "presented", id };
|
|
104
|
+
}
|
|
105
|
+
if (pending.size >= maximumPendingForms)
|
|
106
|
+
return { kind: "refused", reason: "capacity" };
|
|
107
|
+
const id = correlationKey();
|
|
108
|
+
pending.set(id, { sessionId, approval, approvals, answering: false });
|
|
109
|
+
await publish?.(sessionId);
|
|
110
|
+
return { kind: "presented", id };
|
|
111
|
+
}
|
|
112
|
+
function bindSession(sessionId, session, attachment) {
|
|
113
|
+
if (attachment === undefined)
|
|
114
|
+
return session;
|
|
115
|
+
return {
|
|
116
|
+
...session,
|
|
117
|
+
extension: (id) => {
|
|
118
|
+
const client = session.extension(id);
|
|
119
|
+
return {
|
|
120
|
+
...client,
|
|
121
|
+
call: (command, input) => id === mcpFormApprovalContract.id && command === "answer"
|
|
122
|
+
? runLocalUiCommand(attachment, sessionId, () => client.call(command, input))
|
|
123
|
+
: client.call(command, input),
|
|
124
|
+
};
|
|
125
|
+
},
|
|
126
|
+
watch: async (input) => {
|
|
127
|
+
const stopWatching = startLocalUiWatch(attachment, sessionId);
|
|
128
|
+
if (stopWatching === undefined)
|
|
129
|
+
return session.watch(input);
|
|
130
|
+
try {
|
|
131
|
+
const watch = await session.watch(input);
|
|
132
|
+
return {
|
|
133
|
+
...watch,
|
|
134
|
+
close: async () => {
|
|
135
|
+
stopWatching();
|
|
136
|
+
await watch.close();
|
|
137
|
+
},
|
|
138
|
+
};
|
|
139
|
+
}
|
|
140
|
+
catch (error) {
|
|
141
|
+
stopWatching();
|
|
142
|
+
throw error;
|
|
143
|
+
}
|
|
144
|
+
},
|
|
145
|
+
};
|
|
146
|
+
}
|
|
147
|
+
return { extension, presentApproval, bindSession };
|
|
148
|
+
}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import { decodeJwt } from "jose";
|
|
2
|
+
import { z } from "zod";
|
|
3
|
+
/**
|
|
4
|
+
* Why a sign-in proved no person: the token or claim at fault and the claim
|
|
5
|
+
* NAMES present. Never claim values, emails, subjects or tokens.
|
|
6
|
+
*/
|
|
7
|
+
export declare const openAIIdentityDetailSchema: z.ZodObject<{
|
|
8
|
+
claim: z.ZodEnum<{
|
|
9
|
+
access_token: "access_token";
|
|
10
|
+
"access_token.iss": "access_token.iss";
|
|
11
|
+
"access_token.sub": "access_token.sub";
|
|
12
|
+
}>;
|
|
13
|
+
problem: z.ZodEnum<{
|
|
14
|
+
absent: "absent";
|
|
15
|
+
malformed: "malformed";
|
|
16
|
+
invalid: "invalid";
|
|
17
|
+
}>;
|
|
18
|
+
claimNames: z.ZodArray<z.ZodString>;
|
|
19
|
+
authClaimNames: z.ZodOptional<z.ZodArray<z.ZodString>>;
|
|
20
|
+
}, z.core.$strict>;
|
|
21
|
+
export type OpenAIIdentityDetail = z.output<typeof openAIIdentityDetailSchema>;
|
|
22
|
+
/** The person a direct-access token belongs to: its issuer and subject. */
|
|
23
|
+
export declare function readOpenAIPerson(access: string, decode?: typeof decodeJwt): Readonly<{
|
|
24
|
+
kind: "person";
|
|
25
|
+
iss: string;
|
|
26
|
+
sub: string;
|
|
27
|
+
}> | Readonly<{
|
|
28
|
+
kind: "unproven";
|
|
29
|
+
detail: OpenAIIdentityDetail;
|
|
30
|
+
}>;
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
import { decodeJwt, errors } from "jose";
|
|
2
|
+
import { z } from "zod";
|
|
3
|
+
const identityComponent = z
|
|
4
|
+
.string()
|
|
5
|
+
.min(1)
|
|
6
|
+
.max(256)
|
|
7
|
+
.refine((value) => value.trim().length > 0);
|
|
8
|
+
const authNamespace = "https://api.openai.com/auth";
|
|
9
|
+
const claimObjectSchema = z.record(z.string(), z.json());
|
|
10
|
+
/** At most this many claim names, each at most this long, ever cross. */
|
|
11
|
+
const claimNameLimit = 32;
|
|
12
|
+
const claimNameLength = 64;
|
|
13
|
+
const claimNamesSchema = z
|
|
14
|
+
.array(z.string().max(claimNameLength))
|
|
15
|
+
.max(claimNameLimit);
|
|
16
|
+
/**
|
|
17
|
+
* Why a sign-in proved no person: the token or claim at fault and the claim
|
|
18
|
+
* NAMES present. Never claim values, emails, subjects or tokens.
|
|
19
|
+
*/
|
|
20
|
+
export const openAIIdentityDetailSchema = z.strictObject({
|
|
21
|
+
claim: z.enum(["access_token", "access_token.iss", "access_token.sub"]),
|
|
22
|
+
problem: z.enum(["malformed", "absent", "invalid"]),
|
|
23
|
+
claimNames: claimNamesSchema,
|
|
24
|
+
authClaimNames: claimNamesSchema.optional(),
|
|
25
|
+
});
|
|
26
|
+
const names = (claims) => Object.keys(claims)
|
|
27
|
+
.slice(0, claimNameLimit)
|
|
28
|
+
.map((name) => name.slice(0, claimNameLength));
|
|
29
|
+
/** Decode JWT claims; only jose's JWTInvalid means a malformed token. */
|
|
30
|
+
function claimsOf(token, decode) {
|
|
31
|
+
try {
|
|
32
|
+
return claimObjectSchema.parse(decode(token));
|
|
33
|
+
}
|
|
34
|
+
catch (error) {
|
|
35
|
+
// Anything other than JWTInvalid is a real fault, not a malformed token.
|
|
36
|
+
if (error instanceof errors.JWTInvalid)
|
|
37
|
+
return undefined;
|
|
38
|
+
throw error;
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
/** The person a direct-access token belongs to: its issuer and subject. */
|
|
42
|
+
export function readOpenAIPerson(access, decode = decodeJwt) {
|
|
43
|
+
const claims = claimsOf(access, decode);
|
|
44
|
+
if (claims === undefined)
|
|
45
|
+
return {
|
|
46
|
+
kind: "unproven",
|
|
47
|
+
detail: { claim: "access_token", problem: "malformed", claimNames: [] },
|
|
48
|
+
};
|
|
49
|
+
const claimNames = names(claims);
|
|
50
|
+
const auth = claimObjectSchema.safeParse(claims[authNamespace]);
|
|
51
|
+
const context = auth.success
|
|
52
|
+
? { claimNames, authClaimNames: names(auth.data) }
|
|
53
|
+
: { claimNames };
|
|
54
|
+
const unproven = (claim, key) => ({
|
|
55
|
+
kind: "unproven",
|
|
56
|
+
detail: {
|
|
57
|
+
claim,
|
|
58
|
+
problem: key in claims ? "invalid" : "absent",
|
|
59
|
+
...context,
|
|
60
|
+
},
|
|
61
|
+
});
|
|
62
|
+
const iss = identityComponent.safeParse(claims.iss);
|
|
63
|
+
if (!iss.success)
|
|
64
|
+
return unproven("access_token.iss", "iss");
|
|
65
|
+
const sub = identityComponent.safeParse(claims.sub);
|
|
66
|
+
if (!sub.success)
|
|
67
|
+
return unproven("access_token.sub", "sub");
|
|
68
|
+
return { kind: "person", iss: iss.data, sub: sub.data };
|
|
69
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { UnauthorizedError, type AuthProvider } from "@modelcontextprotocol/client";
|
|
2
|
+
/** A selected Oyster refused its key; never fall through to Hub OAuth. */
|
|
3
|
+
export declare class OysterMcpKeyRejectedError extends UnauthorizedError {
|
|
4
|
+
constructor();
|
|
5
|
+
}
|
|
6
|
+
/** Read bounded UTF-8 stdin, then place the key in Local's existing private store. */
|
|
7
|
+
export declare function saveOysterMcpKey(endpoint: URL, input: AsyncIterable<Uint8Array>, options?: {
|
|
8
|
+
credentialPath?: string;
|
|
9
|
+
}): Promise<void>;
|
|
10
|
+
/** Static bearer auth reads only the selected origin's credential, without discovery. */
|
|
11
|
+
export declare function oysterMcpKeyAuth(endpoint: URL, options?: {
|
|
12
|
+
credentialPath?: string;
|
|
13
|
+
}): AuthProvider;
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
import { UnauthorizedError, } from "@modelcontextprotocol/client";
|
|
2
|
+
import { z } from "zod";
|
|
3
|
+
import { createPrivateCredentialStore, maximumCredentialStringLength, } from "./private-file-credential-store.js";
|
|
4
|
+
import { parseFungiMcpEndpoint } from "./private-fungi-mcp-endpoint.js";
|
|
5
|
+
import { capturePrivateLocalConfigRoot } from "./private-local-config-root.js";
|
|
6
|
+
/** A selected Oyster refused its key; never fall through to Hub OAuth. */
|
|
7
|
+
export class OysterMcpKeyRejectedError extends UnauthorizedError {
|
|
8
|
+
constructor() {
|
|
9
|
+
super("The Oyster MCP key was refused. Replace it with fungi mcp use <url> --from-file -.");
|
|
10
|
+
this.name = "OysterMcpKeyRejectedError";
|
|
11
|
+
}
|
|
12
|
+
}
|
|
13
|
+
function credentialId(endpoint) {
|
|
14
|
+
return `oyster-mcp:${parseFungiMcpEndpoint(endpoint).origin}`;
|
|
15
|
+
}
|
|
16
|
+
// Ear Tag's Oyster producer uses the oyk family and nanoid's URL-safe alphabet.
|
|
17
|
+
const oysterKey = z
|
|
18
|
+
.string()
|
|
19
|
+
.trim()
|
|
20
|
+
.min(5)
|
|
21
|
+
.max(maximumCredentialStringLength)
|
|
22
|
+
.regex(/^oyk_[A-Za-z0-9_-]+$/);
|
|
23
|
+
/** Read bounded UTF-8 stdin, then place the key in Local's existing private store. */
|
|
24
|
+
export async function saveOysterMcpKey(endpoint, input, options = {}) {
|
|
25
|
+
const id = credentialId(endpoint);
|
|
26
|
+
const store = createPrivateCredentialStore(options.credentialPath ?? capturePrivateLocalConfigRoot().credentialPath);
|
|
27
|
+
const decoder = new TextDecoder("utf-8", { fatal: true });
|
|
28
|
+
let bytes = 0;
|
|
29
|
+
let text = "";
|
|
30
|
+
for await (const chunk of input) {
|
|
31
|
+
bytes += chunk.byteLength;
|
|
32
|
+
if (bytes > maximumCredentialStringLength)
|
|
33
|
+
throw new Error("Oyster key input is too large");
|
|
34
|
+
text += decoder.decode(chunk, { stream: true });
|
|
35
|
+
}
|
|
36
|
+
text += decoder.decode();
|
|
37
|
+
const parsed = oysterKey.safeParse(text);
|
|
38
|
+
if (!parsed.success)
|
|
39
|
+
throw new Error("Expected one Oyster oyk_ key on stdin");
|
|
40
|
+
await store.modify(id, () => Promise.resolve({ type: "api_key", key: parsed.data }));
|
|
41
|
+
}
|
|
42
|
+
/** Static bearer auth reads only the selected origin's credential, without discovery. */
|
|
43
|
+
export function oysterMcpKeyAuth(endpoint, options = {}) {
|
|
44
|
+
const id = credentialId(endpoint);
|
|
45
|
+
const store = createPrivateCredentialStore(options.credentialPath ?? capturePrivateLocalConfigRoot().credentialPath);
|
|
46
|
+
return {
|
|
47
|
+
async token() {
|
|
48
|
+
const credential = await store.read(id);
|
|
49
|
+
if (credential?.type !== "api_key" || credential.key === undefined)
|
|
50
|
+
throw new OysterMcpKeyRejectedError();
|
|
51
|
+
return credential.key;
|
|
52
|
+
},
|
|
53
|
+
onUnauthorized() {
|
|
54
|
+
return Promise.reject(new OysterMcpKeyRejectedError());
|
|
55
|
+
},
|
|
56
|
+
};
|
|
57
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import type { AssistantMessage } from "@earendil-works/pi-ai";
|
|
2
|
+
import type { ProviderRejection } from "@fungi.computer/mule";
|
|
3
|
+
/** Evidence owned by one Local request, never inferred from assistant prose. */
|
|
4
|
+
export type ModelFailureInput = Readonly<{
|
|
5
|
+
refusal?: Readonly<{
|
|
6
|
+
category: string;
|
|
7
|
+
message: string;
|
|
8
|
+
}> | undefined;
|
|
9
|
+
upstream?: AssistantMessage | undefined;
|
|
10
|
+
rejection?: ProviderRejection | undefined;
|
|
11
|
+
}>;
|
|
12
|
+
/** Local retries only a typed transient, never cancellation or a turn refusal. */
|
|
13
|
+
export declare function modelFailureRetryable(failure: ModelFailureInput): boolean;
|
|
14
|
+
/** The one owned wording for a failed Local model request. */
|
|
15
|
+
export declare function modelFailureMessage(provider: string, failure: ModelFailureInput): string;
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { isContextOverflow } from "@earendil-works/pi-ai/utils/overflow";
|
|
2
|
+
/** Local retries only a typed transient, never cancellation or a turn refusal. */
|
|
3
|
+
export function modelFailureRetryable(failure) {
|
|
4
|
+
return (failure.refusal === undefined &&
|
|
5
|
+
failure.upstream?.stopReason !== "aborted" &&
|
|
6
|
+
failure.rejection?.code === "provider_unavailable");
|
|
7
|
+
}
|
|
8
|
+
/** The one owned wording for a failed Local model request. */
|
|
9
|
+
export function modelFailureMessage(provider, failure) {
|
|
10
|
+
if (failure.refusal !== undefined)
|
|
11
|
+
return `${failure.refusal.category}: ${failure.refusal.message}`;
|
|
12
|
+
// Typed subscription evidence wins over all SDK/provider wording.
|
|
13
|
+
if (failure.rejection?.code === "usage-limit-reached")
|
|
14
|
+
return `${provider} usage limit reached (usage-limit-reached). Check ChatGPT Settings > Usage, then try again later.`;
|
|
15
|
+
if (modelFailureRetryable(failure))
|
|
16
|
+
return `${provider} service unavailable. Try again shortly.`;
|
|
17
|
+
if (failure.upstream !== undefined) {
|
|
18
|
+
if (isContextOverflow(failure.upstream))
|
|
19
|
+
return `${provider} request exceeds the context window. Compact the Session or choose another model.`;
|
|
20
|
+
}
|
|
21
|
+
if (failure.rejection?.code === "provider_rejected")
|
|
22
|
+
return `${provider} rejected the model request (${failure.rejection.code}, HTTP ${String(failure.rejection.status)}). Check /login or choose another model with /model.`;
|
|
23
|
+
return `${provider} rejected the model request. Check /login or choose another model with /model.`;
|
|
24
|
+
}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import { DatabaseSync } from "node:sqlite";
|
|
2
|
+
import type { SqliteOwner } from "@fungi.computer/shiitake";
|
|
3
|
+
export type NativeOutput = null | number | bigint | string | Uint8Array;
|
|
4
|
+
export type NativeInput = null | number | string | Uint8Array;
|
|
5
|
+
/** Bun keeps its native transaction and deterministic statement-close APIs. */
|
|
6
|
+
export type PrivateBunSqliteDatabase = {
|
|
7
|
+
readonly isTransaction: boolean;
|
|
8
|
+
exec: (statement: string) => void;
|
|
9
|
+
prepare: (statement: string) => {
|
|
10
|
+
all: (...bindings: NativeInput[]) => Record<string, NativeOutput>[];
|
|
11
|
+
get: (...bindings: NativeInput[]) => Record<string, NativeOutput> | undefined;
|
|
12
|
+
run: (...bindings: NativeInput[]) => {
|
|
13
|
+
changes: number | bigint;
|
|
14
|
+
};
|
|
15
|
+
};
|
|
16
|
+
transactionSync: SqliteOwner["transactionSync"];
|
|
17
|
+
close: () => void;
|
|
18
|
+
};
|
|
19
|
+
/** The actual Node connection or Local's explicit native Bun adapter. */
|
|
20
|
+
export type PrivateSqliteDatabase = DatabaseSync | PrivateBunSqliteDatabase;
|
|
21
|
+
/**
|
|
22
|
+
* A listing owns this database until its existing finally close. Bun's
|
|
23
|
+
* node:sqlite prepare/close leaves native statements alive until GC. Native
|
|
24
|
+
* close(true) on the native Bun driver explicitly finalizes every statement
|
|
25
|
+
* before releasing the connection, including when a read or decoding fails.
|
|
26
|
+
*/
|
|
27
|
+
export declare function openPrivateReadOnlySqliteDatabase(path: string): PrivateSqliteDatabase;
|
|
28
|
+
/** Writable connections own their statements through deterministic close. */
|
|
29
|
+
export declare function openPrivateWritableSqliteDatabase(path: string, timeout: number): PrivateSqliteDatabase;
|
|
30
|
+
export type PrivateSqliteFailureCode = "busy" | "locked" | "corrupt" | "not_a_database" | "other";
|
|
31
|
+
/** Decode the maintained drivers' structured result codes before sanitizing. */
|
|
32
|
+
export declare function privateSqliteFailureCode(cause: unknown): PrivateSqliteFailureCode | undefined;
|
|
33
|
+
/**
|
|
34
|
+
* Keep Local's native failure reporting around a host-supplied SQL owner.
|
|
35
|
+
* The supplied owner retains transaction mechanics and connection lifetime.
|
|
36
|
+
*/
|
|
37
|
+
export declare function observePrivateSqliteOwner(owner: SqliteOwner, onFailure?: (code: PrivateSqliteFailureCode) => void): SqliteOwner;
|
|
38
|
+
/** Adapt one synchronous native database to Shiitake's public owner shape. */
|
|
39
|
+
export declare function createPrivateSqliteOwner(database: PrivateSqliteDatabase, onFailure?: (code: PrivateSqliteFailureCode) => void): SqliteOwner;
|