@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.
Files changed (207) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +542 -2
  3. package/computerd-loopback.cjs +16 -0
  4. package/dist/auth-cli.d.ts +46 -0
  5. package/dist/auth-cli.js +373 -0
  6. package/dist/fungi-account-command.d.ts +18 -0
  7. package/dist/fungi-account-command.js +95 -0
  8. package/dist/index.d.ts +27 -0
  9. package/dist/index.js +23 -0
  10. package/dist/internal/image-fit-metafile.json +106 -0
  11. package/dist/internal/local-image-fit-worker.mjs +960 -0
  12. package/dist/internal/private-workerd-sandbox-worker.mjs +27487 -0
  13. package/dist/internal/sandbox-metafile.json +6726 -0
  14. package/dist/linear-auth.d.ts +4 -0
  15. package/dist/linear-auth.js +72 -0
  16. package/dist/local-archive-handoff.d.ts +12 -0
  17. package/dist/local-archive-handoff.js +21 -0
  18. package/dist/local-extensions.d.ts +143 -0
  19. package/dist/local-extensions.js +624 -0
  20. package/dist/local-image-fit.d.ts +30 -0
  21. package/dist/local-image-fit.js +105 -0
  22. package/dist/local-login.d.ts +55 -0
  23. package/dist/local-login.js +167 -0
  24. package/dist/local-models.d.ts +16 -0
  25. package/dist/local-models.js +82 -0
  26. package/dist/local-session-archives.d.ts +8 -0
  27. package/dist/local-session-archives.js +152 -0
  28. package/dist/local-skill-source.d.ts +21 -0
  29. package/dist/local-skill-source.js +433 -0
  30. package/dist/log-event.d.ts +9 -0
  31. package/dist/log-event.js +13 -0
  32. package/dist/private-bounded-response.d.ts +2 -0
  33. package/dist/private-bounded-response.js +41 -0
  34. package/dist/private-bun-shiitake-host.d.ts +11 -0
  35. package/dist/private-bun-shiitake-host.js +66 -0
  36. package/dist/private-file-credential-store.d.ts +58 -0
  37. package/dist/private-file-credential-store.js +331 -0
  38. package/dist/private-files-patch.d.ts +4 -0
  39. package/dist/private-files-patch.js +104 -0
  40. package/dist/private-fungi-account.d.ts +252 -0
  41. package/dist/private-fungi-account.js +1329 -0
  42. package/dist/private-fungi-auth-error.d.ts +5 -0
  43. package/dist/private-fungi-auth-error.js +7 -0
  44. package/dist/private-fungi-mcp-connection.d.ts +26 -0
  45. package/dist/private-fungi-mcp-connection.js +162 -0
  46. package/dist/private-fungi-mcp-endpoint.d.ts +2 -0
  47. package/dist/private-fungi-mcp-endpoint.js +15 -0
  48. package/dist/private-fungi-mcp-selection-schema.d.ts +7 -0
  49. package/dist/private-fungi-mcp-selection-schema.js +15 -0
  50. package/dist/private-fungi-mcp-selection.d.ts +14 -0
  51. package/dist/private-fungi-mcp-selection.js +49 -0
  52. package/dist/private-fungi-model-api.d.ts +6 -0
  53. package/dist/private-fungi-model-api.js +125 -0
  54. package/dist/private-fungi-model-catalog.d.ts +16 -0
  55. package/dist/private-fungi-model-catalog.js +86 -0
  56. package/dist/private-fungi-model-provider.d.ts +6 -0
  57. package/dist/private-fungi-model-provider.js +51 -0
  58. package/dist/private-fungi-pending-logouts.d.ts +34 -0
  59. package/dist/private-fungi-pending-logouts.js +135 -0
  60. package/dist/private-fungi-service-logout.d.ts +44 -0
  61. package/dist/private-fungi-service-logout.js +59 -0
  62. package/dist/private-invocation-computer.d.ts +58 -0
  63. package/dist/private-invocation-computer.js +498 -0
  64. package/dist/private-invocation-files.d.ts +30 -0
  65. package/dist/private-invocation-files.js +520 -0
  66. package/dist/private-local-acp-stdio.d.ts +17 -0
  67. package/dist/private-local-acp-stdio.js +145 -0
  68. package/dist/private-local-admission-gate.d.ts +14 -0
  69. package/dist/private-local-admission-gate.js +52 -0
  70. package/dist/private-local-agent-control.d.ts +101 -0
  71. package/dist/private-local-agent-control.js +30 -0
  72. package/dist/private-local-agent-default-model.d.ts +12 -0
  73. package/dist/private-local-agent-default-model.js +31 -0
  74. package/dist/private-local-auth-rpc.d.ts +129 -0
  75. package/dist/private-local-auth-rpc.js +396 -0
  76. package/dist/private-local-bwrap.d.ts +13 -0
  77. package/dist/private-local-bwrap.js +70 -0
  78. package/dist/private-local-client-version.d.ts +5 -0
  79. package/dist/private-local-client-version.js +43 -0
  80. package/dist/private-local-computer-attach.d.ts +49 -0
  81. package/dist/private-local-computer-attach.js +230 -0
  82. package/dist/private-local-computer-backend.d.ts +282 -0
  83. package/dist/private-local-computer-backend.js +402 -0
  84. package/dist/private-local-computer-processes.d.ts +202 -0
  85. package/dist/private-local-computer-processes.js +613 -0
  86. package/dist/private-local-config-root.d.ts +21 -0
  87. package/dist/private-local-config-root.js +19 -0
  88. package/dist/private-local-containment.d.ts +51 -0
  89. package/dist/private-local-containment.js +161 -0
  90. package/dist/private-local-default-model.d.ts +11 -0
  91. package/dist/private-local-default-model.js +45 -0
  92. package/dist/private-local-delegation.d.ts +36 -0
  93. package/dist/private-local-delegation.js +133 -0
  94. package/dist/private-local-effective-model.d.ts +27 -0
  95. package/dist/private-local-effective-model.js +17 -0
  96. package/dist/private-local-exec-environment.d.ts +16 -0
  97. package/dist/private-local-exec-environment.js +11 -0
  98. package/dist/private-local-exec-launch.d.ts +41 -0
  99. package/dist/private-local-exec-launch.js +51 -0
  100. package/dist/private-local-exec-path.d.ts +6 -0
  101. package/dist/private-local-exec-path.js +40 -0
  102. package/dist/private-local-git-exclude.d.ts +2 -0
  103. package/dist/private-local-git-exclude.js +33 -0
  104. package/dist/private-local-http-dispatch.d.ts +5 -0
  105. package/dist/private-local-http-dispatch.js +14 -0
  106. package/dist/private-local-linux-sandbox.d.ts +8 -0
  107. package/dist/private-local-linux-sandbox.js +67 -0
  108. package/dist/private-local-mail.d.ts +13 -0
  109. package/dist/private-local-mail.js +77 -0
  110. package/dist/private-local-mesh-enrollment.d.ts +34 -0
  111. package/dist/private-local-mesh-enrollment.js +188 -0
  112. package/dist/private-local-mesh-links.d.ts +21 -0
  113. package/dist/private-local-mesh-links.js +229 -0
  114. package/dist/private-local-mesh-record.d.ts +120 -0
  115. package/dist/private-local-mesh-record.js +55 -0
  116. package/dist/private-local-process-identity.d.ts +20 -0
  117. package/dist/private-local-process-identity.js +40 -0
  118. package/dist/private-local-runtime-host.d.ts +192 -0
  119. package/dist/private-local-runtime-host.js +1177 -0
  120. package/dist/private-local-sandbox-policy.d.ts +21 -0
  121. package/dist/private-local-sandbox-policy.js +121 -0
  122. package/dist/private-local-sandbox.d.ts +37 -0
  123. package/dist/private-local-sandbox.js +114 -0
  124. package/dist/private-local-seatbelt.d.ts +24 -0
  125. package/dist/private-local-seatbelt.js +68 -0
  126. package/dist/private-local-service-contract.d.ts +170 -0
  127. package/dist/private-local-service-contract.js +1 -0
  128. package/dist/private-local-service-discovery.d.ts +217 -0
  129. package/dist/private-local-service-discovery.js +322 -0
  130. package/dist/private-local-service-ids.d.ts +3 -0
  131. package/dist/private-local-service-ids.js +8 -0
  132. package/dist/private-local-service-lease.d.ts +16 -0
  133. package/dist/private-local-service-lease.js +105 -0
  134. package/dist/private-local-service-listener.d.ts +22 -0
  135. package/dist/private-local-service-listener.js +407 -0
  136. package/dist/private-local-service-log.d.ts +128 -0
  137. package/dist/private-local-service-log.js +321 -0
  138. package/dist/private-local-service-owner.d.ts +135 -0
  139. package/dist/private-local-service-owner.js +1369 -0
  140. package/dist/private-local-service-process.d.ts +1 -0
  141. package/dist/private-local-service-process.js +8 -0
  142. package/dist/private-local-service-termination.d.ts +7 -0
  143. package/dist/private-local-service-termination.js +67 -0
  144. package/dist/private-local-service.d.ts +46 -0
  145. package/dist/private-local-service.js +606 -0
  146. package/dist/private-local-session-discovery.d.ts +66 -0
  147. package/dist/private-local-session-discovery.js +278 -0
  148. package/dist/private-local-shiitake-host.d.ts +44 -0
  149. package/dist/private-local-shiitake-host.js +143 -0
  150. package/dist/private-local-startup-model.d.ts +51 -0
  151. package/dist/private-local-startup-model.js +86 -0
  152. package/dist/private-local-stop-all.d.ts +27 -0
  153. package/dist/private-local-stop-all.js +26 -0
  154. package/dist/private-local-ui-attachment.d.ts +19 -0
  155. package/dist/private-local-ui-attachment.js +71 -0
  156. package/dist/private-local-worker-server.d.ts +16 -0
  157. package/dist/private-local-worker-server.js +144 -0
  158. package/dist/private-mcp-form-contract.d.ts +186 -0
  159. package/dist/private-mcp-form-contract.js +53 -0
  160. package/dist/private-mcp-form-elicitation.d.ts +18 -0
  161. package/dist/private-mcp-form-elicitation.js +97 -0
  162. package/dist/private-mcp-form-session.d.ts +172 -0
  163. package/dist/private-mcp-form-session.js +148 -0
  164. package/dist/private-openai-claims.d.ts +30 -0
  165. package/dist/private-openai-claims.js +69 -0
  166. package/dist/private-oyster-mcp-auth.d.ts +13 -0
  167. package/dist/private-oyster-mcp-auth.js +57 -0
  168. package/dist/private-provider-rejection.d.ts +15 -0
  169. package/dist/private-provider-rejection.js +24 -0
  170. package/dist/private-sqlite-owner.d.ts +39 -0
  171. package/dist/private-sqlite-owner.js +169 -0
  172. package/dist/private-team-computers.d.ts +38 -0
  173. package/dist/private-team-computers.js +110 -0
  174. package/dist/private-workerd-sandbox.d.ts +3 -0
  175. package/dist/private-workerd-sandbox.js +347 -0
  176. package/dist/seatbelt/safe-base.sbpl +79 -0
  177. package/dist/subscription-activation.d.ts +9 -0
  178. package/dist/subscription-activation.js +62 -0
  179. package/dist/subscription-identity.d.ts +23 -0
  180. package/dist/subscription-identity.js +124 -0
  181. package/dist/subscription-turn.d.ts +17 -0
  182. package/dist/subscription-turn.js +143 -0
  183. package/dist/team-apps.d.ts +24 -0
  184. package/dist/team-apps.js +128 -0
  185. package/dist/tracker-git-credential.d.ts +15 -0
  186. package/dist/tracker-git-credential.js +87 -0
  187. package/native/bin/LICENSE.bubblewrap +502 -0
  188. package/native/bin/LICENSE.libcap +112 -0
  189. package/native/bin/LICENSE.sandbox-runtime +201 -0
  190. package/native/bin/SOURCE.bubblewrap.tar.xz +0 -0
  191. package/native/bin/build.json +4037 -0
  192. package/native/bin/bwrap-build-source/Dockerfile +48 -0
  193. package/native/bin/bwrap-build-source/README.md +96 -0
  194. package/native/bin/bwrap-build-source/build.mjs +52 -0
  195. package/native/bin/bwrap-build-source/linux-arm64.ini +14 -0
  196. package/native/bin/bwrap-build-source/pin.mjs +21 -0
  197. package/native/bin/bwrap-build-source/record.mjs +74 -0
  198. package/native/bin/bwrap-build-source/vendor/LICENSE.sandbox-runtime +201 -0
  199. package/native/bin/bwrap-build-source/vendor/README.md +23 -0
  200. package/native/bin/bwrap-build-source/vendor/seccomp-unix-block.c +280 -0
  201. package/native/bin/bwrap-build.json +44 -0
  202. package/native/bin/fungi-apply-patch +0 -0
  203. package/native/bin/fungi-bwrap +0 -0
  204. package/native/bin/fungi-bwrap.sha256 +1 -0
  205. package/native/bin/fungi-seccomp.bpf +0 -0
  206. package/native/bin/fungi-seccomp.bpf.sha256 +1 -0
  207. package/package.json +161 -3
@@ -0,0 +1,71 @@
1
+ import { AsyncLocalStorage } from "node:async_hooks";
2
+ import { z } from "zod";
3
+ export const localUiAttachmentLifetimeSchema = z.enum(["watch", "connection"]);
4
+ const attachments = new WeakMap();
5
+ export function createLocalUiAttachment(sessionId, lifetime = "watch") {
6
+ const controller = new AbortController();
7
+ const attachment = {
8
+ sessionId,
9
+ signal: controller.signal,
10
+ close: () => {
11
+ attachments.delete(attachment);
12
+ controller.abort();
13
+ },
14
+ };
15
+ attachments.set(attachment, { watches: 0, lifetime });
16
+ return attachment;
17
+ }
18
+ export function startLocalUiWatch(attachment, sessionId) {
19
+ const state = attachments.get(attachment);
20
+ if (state === undefined || attachment.sessionId !== sessionId)
21
+ return undefined;
22
+ state.watches += 1;
23
+ let watching = true;
24
+ return () => {
25
+ if (!watching)
26
+ return;
27
+ watching = false;
28
+ state.watches -= 1;
29
+ // TUI/form authority is watch-scoped. ACP closes each prompt's watch,
30
+ // but its host-issued route belongs to the retained connection binding.
31
+ if (state.lifetime === "watch")
32
+ attachment.close();
33
+ };
34
+ }
35
+ const commands = new AsyncLocalStorage();
36
+ function isAttached(command) {
37
+ return (command.attachment.sessionId === command.sessionId &&
38
+ (attachments.get(command.attachment)?.watches ?? 0) > 0);
39
+ }
40
+ /** Authority lasts for one command, not model work that might wake later. */
41
+ export function runLocalUiCommand(attachment, sessionId, body) {
42
+ const command = { attachment, sessionId, available: true };
43
+ return commands.run(command, async () => {
44
+ let result;
45
+ try {
46
+ result = await body();
47
+ }
48
+ catch (cause) {
49
+ command.available = false;
50
+ await command.complete?.(false);
51
+ throw cause;
52
+ }
53
+ command.available = false;
54
+ return command.complete === undefined
55
+ ? result
56
+ : command.complete(isAttached(command));
57
+ });
58
+ }
59
+ /** Consume host authority before registering the post-commit continuation. */
60
+ export function consumeLocalUiCommand(sessionId) {
61
+ const command = commands.getStore();
62
+ if (command === undefined ||
63
+ !command.available ||
64
+ command.sessionId !== sessionId ||
65
+ !isAttached(command))
66
+ return undefined;
67
+ command.available = false;
68
+ return (complete) => {
69
+ command.complete = complete;
70
+ };
71
+ }
@@ -0,0 +1,16 @@
1
+ import type { openPrivateLocalBunShiitakeWorkerBatch } from "./private-bun-shiitake-host.js";
2
+ export type ShutdownSignal = "SIGINT" | "SIGTERM";
3
+ export type Signals = Readonly<{
4
+ on: (signal: ShutdownSignal, listener: () => void) => void;
5
+ off: (signal: ShutdownSignal, listener: () => void) => void;
6
+ }>;
7
+ export type Output = Pick<NodeJS.WritableStream, "write">;
8
+ export type OpenWorkerBatch = typeof openPrivateLocalBunShiitakeWorkerBatch;
9
+ export type PrivateLocalWorkerServerIo = Readonly<{
10
+ cwd: string;
11
+ home?: string;
12
+ stdout?: Output | undefined;
13
+ signals?: Signals | undefined;
14
+ }>;
15
+ /** Retained explicit static worker batch; ordinary clients use the persistent manager. */
16
+ export declare function runPrivateLocalWorkerServer(workersFile: string, listen: string, io: PrivateLocalWorkerServerIo, openBatch?: OpenWorkerBatch): Promise<void>;
@@ -0,0 +1,144 @@
1
+ import { logEvent } from "./log-event.js";
2
+ import { capturePrivateLocalConfigRoot, } from "./private-local-config-root.js";
3
+ import { readFungiMcpSelection } from "./private-fungi-mcp-selection.js";
4
+ import { fungiMcpSelectionSchema } from "./private-fungi-mcp-selection-schema.js";
5
+ import { readFile } from "node:fs/promises";
6
+ import process from "node:process";
7
+ import { dirname, resolve } from "node:path";
8
+ import { z } from "zod";
9
+ const reasoningSchema = z.enum([
10
+ "minimal",
11
+ "low",
12
+ "medium",
13
+ "high",
14
+ "xhigh",
15
+ "max",
16
+ ]);
17
+ const ownerSchema = z.strictObject({
18
+ invocationRoot: z.string().min(1),
19
+ databasePath: z.string().min(1).optional(),
20
+ modelId: z.string().min(1).optional(),
21
+ reasoning: reasoningSchema.optional(),
22
+ fungiMcp: fungiMcpSelectionSchema.optional(),
23
+ });
24
+ const workerSchema = z.strictObject({
25
+ name: z.string(),
26
+ owner: ownerSchema,
27
+ });
28
+ const workersSchema = z.array(workerSchema).min(1);
29
+ function parseWorkers(value, filePath) {
30
+ const decoded = workersSchema.safeParse(value);
31
+ if (!decoded.success)
32
+ throw new Error("Invalid workers file: " + filePath);
33
+ const root = dirname(filePath);
34
+ return decoded.data.map(({ name, owner }) => ({
35
+ name,
36
+ owner: {
37
+ ...owner,
38
+ invocationRoot: resolve(root, owner.invocationRoot),
39
+ },
40
+ }));
41
+ }
42
+ async function readWorkers(filePath, cwd, configRoot) {
43
+ const absolutePath = resolve(cwd, filePath);
44
+ let value;
45
+ try {
46
+ value = JSON.parse(await readFile(absolutePath, "utf8"));
47
+ }
48
+ catch (cause) {
49
+ throw new Error("Could not read workers file: " + absolutePath, { cause });
50
+ }
51
+ const workers = [];
52
+ for (const { name, owner } of parseWorkers(value, absolutePath)) {
53
+ const { fungiMcp, ...local } = owner;
54
+ let configured = {
55
+ ...local,
56
+ configRoot,
57
+ };
58
+ if (fungiMcp !== undefined)
59
+ configured = {
60
+ ...configured,
61
+ fungiMcp: readFungiMcpSelection(fungiMcp, {
62
+ credentialPath: configRoot.credentialPath,
63
+ }),
64
+ };
65
+ workers.push({ name, owner: configured });
66
+ }
67
+ return workers;
68
+ }
69
+ function asFailure(cause, message) {
70
+ return cause instanceof Error ? cause : new Error(message, { cause });
71
+ }
72
+ function readyRecord(batch) {
73
+ return JSON.stringify({
74
+ status: "ready",
75
+ baseUrl: batch.baseUrl,
76
+ workers: batch.workers.map(({ name, routePath, databasePath }) => ({
77
+ name,
78
+ routePath,
79
+ databasePath,
80
+ })),
81
+ });
82
+ }
83
+ /** Retained explicit static worker batch; ordinary clients use the persistent manager. */
84
+ export async function runPrivateLocalWorkerServer(workersFile, listen, io, openBatch = async (...args) => {
85
+ const { openPrivateLocalBunShiitakeWorkerBatch } = await import("./private-bun-shiitake-host.js");
86
+ return openPrivateLocalBunShiitakeWorkerBatch(...args);
87
+ }) {
88
+ const root = capturePrivateLocalConfigRoot(io.home);
89
+ const workers = await readWorkers(workersFile, io.cwd, root);
90
+ const stdout = io.stdout ?? process.stdout;
91
+ const signals = io.signals ?? process;
92
+ const stopped = Promise.withResolvers();
93
+ const stop = { requested: false };
94
+ let batch;
95
+ let initiating;
96
+ let closeFailure;
97
+ const onSignal = () => {
98
+ if (stop.requested)
99
+ return;
100
+ stop.requested = true;
101
+ stopped.resolve(undefined);
102
+ };
103
+ signals.on("SIGINT", onSignal);
104
+ signals.on("SIGTERM", onSignal);
105
+ try {
106
+ try {
107
+ batch = await openBatch(workers, listen);
108
+ if (!stop.requested) {
109
+ try {
110
+ stdout.write(readyRecord(batch) + "\n");
111
+ }
112
+ catch (cause) {
113
+ logEvent("worker-server.ready-output", cause);
114
+ initiating = asFailure(cause, "Worker server ready output failed");
115
+ }
116
+ }
117
+ if (initiating === undefined)
118
+ await stopped.promise;
119
+ }
120
+ catch (cause) {
121
+ logEvent("worker-server.opening", cause);
122
+ initiating = asFailure(cause, "Worker server opening failed");
123
+ }
124
+ if (batch !== undefined) {
125
+ try {
126
+ await batch.close();
127
+ }
128
+ catch (cause) {
129
+ logEvent("worker-server.close", cause);
130
+ closeFailure = asFailure(cause, "Worker server close failed");
131
+ }
132
+ }
133
+ }
134
+ finally {
135
+ signals.off("SIGINT", onSignal);
136
+ signals.off("SIGTERM", onSignal);
137
+ }
138
+ if (initiating !== undefined && closeFailure !== undefined)
139
+ throw new AggregateError([initiating, closeFailure], "Worker server startup and close failed");
140
+ if (initiating !== undefined)
141
+ throw initiating;
142
+ if (closeFailure !== undefined)
143
+ throw closeFailure;
144
+ }
@@ -0,0 +1,186 @@
1
+ import * as Schema from "effect/Schema";
2
+ /** The actual MCP form, not a ledger record or an authorization decision. */
3
+ export declare const McpFormApprovalRequestSchema: Schema.Struct<{
4
+ readonly message: Schema.String;
5
+ readonly requestedSchema: Schema.Struct<{
6
+ readonly type: Schema.Literal<"object">;
7
+ readonly properties: Schema.Struct<{
8
+ readonly approve: Schema.Struct<{
9
+ readonly type: Schema.Literal<"boolean">;
10
+ readonly title: Schema.optionalKey<Schema.String>;
11
+ }>;
12
+ }>;
13
+ readonly required: Schema.Tuple<readonly [Schema.Literal<"approve">]>;
14
+ }>;
15
+ }>;
16
+ export type McpFormApprovalRequest = typeof McpFormApprovalRequestSchema.Type;
17
+ /** Human dispositions of one live protocol request. */
18
+ export type McpFormApprovalDecision = "approve" | "decline" | "cancel";
19
+ declare const answerResult: Schema.Union<readonly [Schema.Struct<{
20
+ readonly kind: Schema.Literal<"answered">;
21
+ readonly view: Schema.Struct<{
22
+ readonly record: Schema.Struct<{
23
+ readonly recordId: Schema.String;
24
+ readonly requester: Schema.Struct<{
25
+ readonly principalId: Schema.String;
26
+ readonly agentId: Schema.NullOr<Schema.String>;
27
+ readonly sessionId: Schema.NullOr<Schema.String>;
28
+ readonly displayName: Schema.String;
29
+ }>;
30
+ readonly module: Schema.String;
31
+ readonly operation: Schema.String;
32
+ readonly access: Schema.Literals<readonly ["write", "external"]>;
33
+ readonly description: Schema.Struct<{
34
+ readonly title: Schema.String;
35
+ readonly inputBytes: Schema.Int;
36
+ readonly inputSha256: Schema.String;
37
+ }>;
38
+ readonly evidence: Schema.optionalKey<Schema.$Array<Schema.Struct<{
39
+ readonly kind: Schema.Literal<"artifact">;
40
+ readonly ref: Schema.String;
41
+ readonly mediaType: Schema.Literal<"image/png">;
42
+ readonly bytes: Schema.Int;
43
+ }>>>;
44
+ readonly authorityFence: Schema.String;
45
+ readonly reversibility: Schema.Literals<readonly ["reversible", "irreversible-external"]>;
46
+ readonly state: Schema.Literals<readonly ["requested", "pending", "approved", "claimed", "applied", "denied", "expired", "outcome_unknown"]>;
47
+ readonly by: Schema.NullOr<Schema.Union<readonly [Schema.Struct<{
48
+ readonly kind: Schema.Literal<"human">;
49
+ readonly memberId: Schema.String;
50
+ }>, Schema.Struct<{
51
+ readonly kind: Schema.Literal<"policy">;
52
+ }>]>>;
53
+ readonly reason: Schema.NullOr<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"]>>;
54
+ readonly requestedAt: Schema.Int;
55
+ readonly expiresAt: Schema.Int;
56
+ readonly updatedAt: Schema.Int;
57
+ readonly revision: Schema.Int;
58
+ }>;
59
+ readonly canAnswer: Schema.Boolean;
60
+ readonly canSetAutoApprove: Schema.Boolean;
61
+ }>;
62
+ }>, Schema.Struct<{
63
+ readonly kind: Schema.Literal<"refused">;
64
+ readonly reason: 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"]>;
65
+ }>, Schema.Struct<{
66
+ readonly kind: Schema.Literal<"submitted">;
67
+ }>, Schema.Struct<{
68
+ readonly kind: Schema.Literal<"refused">;
69
+ readonly reason: Schema.Literals<readonly ["not-attached", "not-pending"]>;
70
+ }>]>;
71
+ export type McpFormAnswerResult = typeof answerResult.Type;
72
+ /** The existing Session extension slot carries only display-safe form data. */
73
+ export declare const mcpFormApprovalContract: {
74
+ id: string & import("effect/Brand").Brand<"ExtensionId">;
75
+ commands: {
76
+ answer: {
77
+ scope: "session";
78
+ input: Schema.Struct<{
79
+ readonly id: Schema.String;
80
+ readonly decision: Schema.Literals<readonly ["approve", "deny"]>;
81
+ readonly expectedRevision: Schema.Int;
82
+ }>;
83
+ output: Schema.Union<readonly [Schema.Struct<{
84
+ readonly kind: Schema.Literal<"answered">;
85
+ readonly view: Schema.Struct<{
86
+ readonly record: Schema.Struct<{
87
+ readonly recordId: Schema.String;
88
+ readonly requester: Schema.Struct<{
89
+ readonly principalId: Schema.String;
90
+ readonly agentId: Schema.NullOr<Schema.String>;
91
+ readonly sessionId: Schema.NullOr<Schema.String>;
92
+ readonly displayName: Schema.String;
93
+ }>;
94
+ readonly module: Schema.String;
95
+ readonly operation: Schema.String;
96
+ readonly access: Schema.Literals<readonly ["write", "external"]>;
97
+ readonly description: Schema.Struct<{
98
+ readonly title: Schema.String;
99
+ readonly inputBytes: Schema.Int;
100
+ readonly inputSha256: Schema.String;
101
+ }>;
102
+ readonly evidence: Schema.optionalKey<Schema.$Array<Schema.Struct<{
103
+ readonly kind: Schema.Literal<"artifact">;
104
+ readonly ref: Schema.String;
105
+ readonly mediaType: Schema.Literal<"image/png">;
106
+ readonly bytes: Schema.Int;
107
+ }>>>;
108
+ readonly authorityFence: Schema.String;
109
+ readonly reversibility: Schema.Literals<readonly ["reversible", "irreversible-external"]>;
110
+ readonly state: Schema.Literals<readonly ["requested", "pending", "approved", "claimed", "applied", "denied", "expired", "outcome_unknown"]>;
111
+ readonly by: Schema.NullOr<Schema.Union<readonly [Schema.Struct<{
112
+ readonly kind: Schema.Literal<"human">;
113
+ readonly memberId: Schema.String;
114
+ }>, Schema.Struct<{
115
+ readonly kind: Schema.Literal<"policy">;
116
+ }>]>>;
117
+ readonly reason: Schema.NullOr<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"]>>;
118
+ readonly requestedAt: Schema.Int;
119
+ readonly expiresAt: Schema.Int;
120
+ readonly updatedAt: Schema.Int;
121
+ readonly revision: Schema.Int;
122
+ }>;
123
+ readonly canAnswer: Schema.Boolean;
124
+ readonly canSetAutoApprove: Schema.Boolean;
125
+ }>;
126
+ }>, Schema.Struct<{
127
+ readonly kind: Schema.Literal<"refused">;
128
+ readonly reason: 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"]>;
129
+ }>, Schema.Struct<{
130
+ readonly kind: Schema.Literal<"submitted">;
131
+ }>, Schema.Struct<{
132
+ readonly kind: Schema.Literal<"refused">;
133
+ readonly reason: Schema.Literals<readonly ["not-attached", "not-pending"]>;
134
+ }>]>;
135
+ };
136
+ };
137
+ projection: {
138
+ schema: Schema.Struct<{
139
+ readonly pending: Schema.$Array<Schema.Struct<{
140
+ readonly id: Schema.String;
141
+ readonly approval: Schema.Struct<{
142
+ readonly record: Schema.Struct<{
143
+ readonly recordId: Schema.String;
144
+ readonly requester: Schema.Struct<{
145
+ readonly principalId: Schema.String;
146
+ readonly agentId: Schema.NullOr<Schema.String>;
147
+ readonly sessionId: Schema.NullOr<Schema.String>;
148
+ readonly displayName: Schema.String;
149
+ }>;
150
+ readonly module: Schema.String;
151
+ readonly operation: Schema.String;
152
+ readonly access: Schema.Literals<readonly ["write", "external"]>;
153
+ readonly description: Schema.Struct<{
154
+ readonly title: Schema.String;
155
+ readonly inputBytes: Schema.Int;
156
+ readonly inputSha256: Schema.String;
157
+ }>;
158
+ readonly evidence: Schema.optionalKey<Schema.$Array<Schema.Struct<{
159
+ readonly kind: Schema.Literal<"artifact">;
160
+ readonly ref: Schema.String;
161
+ readonly mediaType: Schema.Literal<"image/png">;
162
+ readonly bytes: Schema.Int;
163
+ }>>>;
164
+ readonly authorityFence: Schema.String;
165
+ readonly reversibility: Schema.Literals<readonly ["reversible", "irreversible-external"]>;
166
+ readonly state: Schema.Literals<readonly ["requested", "pending", "approved", "claimed", "applied", "denied", "expired", "outcome_unknown"]>;
167
+ readonly by: Schema.NullOr<Schema.Union<readonly [Schema.Struct<{
168
+ readonly kind: Schema.Literal<"human">;
169
+ readonly memberId: Schema.String;
170
+ }>, Schema.Struct<{
171
+ readonly kind: Schema.Literal<"policy">;
172
+ }>]>>;
173
+ readonly reason: Schema.NullOr<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"]>>;
174
+ readonly requestedAt: Schema.Int;
175
+ readonly expiresAt: Schema.Int;
176
+ readonly updatedAt: Schema.Int;
177
+ readonly revision: Schema.Int;
178
+ }>;
179
+ readonly canAnswer: Schema.Boolean;
180
+ readonly canSetAutoApprove: Schema.Boolean;
181
+ }>;
182
+ }>>;
183
+ }>;
184
+ };
185
+ };
186
+ export {};
@@ -0,0 +1,53 @@
1
+ import { ExtensionId, } from "@fungi.computer/shiitake/extensions";
2
+ import * as Schema from "effect/Schema";
3
+ import { ApprovalAnswerInputSchema, ApprovalRefusalSchema, ApprovalViewSchema, } from "@fungi.computer/approvals";
4
+ /** The actual MCP form, not a ledger record or an authorization decision. */
5
+ export const McpFormApprovalRequestSchema = Schema.Struct({
6
+ message: Schema.String,
7
+ requestedSchema: Schema.Struct({
8
+ type: Schema.Literal("object"),
9
+ properties: Schema.Struct({
10
+ approve: Schema.Struct({
11
+ type: Schema.Literal("boolean"),
12
+ title: Schema.optionalKey(Schema.String),
13
+ }),
14
+ }),
15
+ required: Schema.Tuple([Schema.Literal("approve")]),
16
+ }),
17
+ });
18
+ const answerResult = Schema.Union([
19
+ Schema.Struct({
20
+ kind: Schema.Literal("answered"),
21
+ view: ApprovalViewSchema,
22
+ }),
23
+ ApprovalRefusalSchema,
24
+ // The attached client awaits the private host's post-commit decision.
25
+ Schema.Struct({ kind: Schema.Literal("submitted") }),
26
+ Schema.Struct({
27
+ kind: Schema.Literal("refused"),
28
+ reason: Schema.Literals(["not-attached", "not-pending"]),
29
+ }),
30
+ ]);
31
+ /** The ephemeral id references the canonical record; it grants no authority. */
32
+ const McpFormPendingSchema = Schema.Struct({
33
+ id: Schema.String,
34
+ approval: ApprovalViewSchema,
35
+ });
36
+ /** The existing Session extension slot carries only display-safe form data. */
37
+ export const mcpFormApprovalContract = {
38
+ id: ExtensionId.make("botanical-local-mcp-form"),
39
+ commands: {
40
+ answer: {
41
+ scope: "session",
42
+ input: Schema.Struct({
43
+ id: Schema.String,
44
+ decision: ApprovalAnswerInputSchema.fields.decision,
45
+ expectedRevision: ApprovalAnswerInputSchema.fields.expectedRevision,
46
+ }),
47
+ output: answerResult,
48
+ },
49
+ },
50
+ projection: {
51
+ schema: Schema.Struct({ pending: Schema.Array(McpFormPendingSchema) }),
52
+ },
53
+ };
@@ -0,0 +1,18 @@
1
+ import type { Client } from "@modelcontextprotocol/client";
2
+ import { type McpFormApprovalRequest, type McpFormApprovalDecision } from "./private-mcp-form-contract.js";
3
+ /** Host-owned human interaction. No callback means immediate cancellation. */
4
+ export type RequestMcpFormApproval = (pending: McpFormApprovalRequest, signal: AbortSignal,
5
+ /** Session captured by source acquisition, never supplied by tool arguments. */
6
+ sessionId: string | undefined) => Promise<McpFormApprovalDecision>;
7
+ /** A refused form is a failed tool call, not an uncertain transport outcome. */
8
+ export declare class McpFormApprovalError extends Error {
9
+ readonly reason: "declined" | "cancelled";
10
+ readonly kind = "mcp_approval_refused";
11
+ constructor(reason: "declined" | "cancelled");
12
+ }
13
+ /** Discriminant carried in a failed MCP tool result's structuredContent. */
14
+ export type McpFormApprovalRefusal = Readonly<Pick<McpFormApprovalError, "kind" | "reason">>;
15
+ export declare function registerMcpFormElicitation(client: Client, requestApproval: RequestMcpFormApproval | undefined, sessionId: string | undefined): {
16
+ close: () => void;
17
+ run: <T>(signal: AbortSignal, invoke: (signal: AbortSignal) => Promise<T>) => Promise<T>;
18
+ };
@@ -0,0 +1,97 @@
1
+ import { AsyncLocalStorage } from "node:async_hooks";
2
+ import { addAbortListener } from "node:events";
3
+ import * as Schema from "effect/Schema";
4
+ import { McpFormApprovalRequestSchema, } from "./private-mcp-form-contract.js";
5
+ const decodePendingForm = Schema.decodeUnknownSync(McpFormApprovalRequestSchema);
6
+ /** A refused form is a failed tool call, not an uncertain transport outcome. */
7
+ export class McpFormApprovalError extends Error {
8
+ reason;
9
+ kind = "mcp_approval_refused";
10
+ constructor(reason) {
11
+ super(`MCP approval was ${reason}.`);
12
+ this.reason = reason;
13
+ this.name = "McpFormApprovalError";
14
+ }
15
+ }
16
+ export function registerMcpFormElicitation(client, requestApproval, sessionId) {
17
+ // The producer sends its form on the requesting tools/call response stream.
18
+ // Keep that call's refusal local even when another call shares this client.
19
+ const calls = new AsyncLocalStorage();
20
+ const lifetime = new AbortController();
21
+ const close = () => {
22
+ lifetime.abort();
23
+ calls.disable();
24
+ };
25
+ client.onclose = close;
26
+ client.registerCapabilities({ elicitation: { form: {} } });
27
+ client.setRequestHandler("elicitation/create", async (request, context) => {
28
+ const call = calls.getStore();
29
+ if (call === undefined || call.failure !== undefined)
30
+ return { action: "cancel" };
31
+ const signal = AbortSignal.any([call.signal, context.mcpReq.signal]);
32
+ let abortListener;
33
+ call.pending++;
34
+ try {
35
+ signal.throwIfAborted();
36
+ const pending = decodePendingForm(request.params);
37
+ const aborted = new Promise((_resolve, reject) => {
38
+ abortListener = addAbortListener(signal, () => {
39
+ reject(new McpFormApprovalError("cancelled"));
40
+ });
41
+ });
42
+ const decision = await Promise.race([
43
+ requestApproval === undefined
44
+ ? Promise.resolve("cancel")
45
+ : Promise.resolve().then(() => requestApproval(pending, signal, sessionId)),
46
+ aborted,
47
+ ]);
48
+ // Cancellation wins over a late Approve, including a UI ignoring abort.
49
+ signal.throwIfAborted();
50
+ if (decision === "approve")
51
+ return { action: "accept", content: { approve: true } };
52
+ call.failure ??= new McpFormApprovalError(decision === "decline" ? "declined" : "cancelled");
53
+ call.cancel();
54
+ return { action: decision };
55
+ }
56
+ catch {
57
+ call.failure ??= new McpFormApprovalError("cancelled");
58
+ call.cancel();
59
+ return { action: "cancel" };
60
+ }
61
+ finally {
62
+ call.pending--;
63
+ abortListener?.[Symbol.dispose]();
64
+ }
65
+ });
66
+ return {
67
+ close,
68
+ run: (signal, invoke) => {
69
+ const settled = new AbortController();
70
+ const call = {
71
+ signal: AbortSignal.any([signal, lifetime.signal, settled.signal]),
72
+ cancel: () => {
73
+ settled.abort();
74
+ },
75
+ pending: 0,
76
+ };
77
+ return calls.run(call, async () => {
78
+ try {
79
+ const result = await invoke(call.signal);
80
+ if (call.failure !== undefined)
81
+ throw call.failure;
82
+ return result;
83
+ }
84
+ catch (cause) {
85
+ if (call.failure !== undefined)
86
+ throw call.failure;
87
+ if (call.pending > 0)
88
+ throw new McpFormApprovalError("cancelled");
89
+ throw cause;
90
+ }
91
+ finally {
92
+ settled.abort();
93
+ }
94
+ });
95
+ },
96
+ };
97
+ }