@fungi.computer/truffle 0.2.2 → 0.3.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/private-files-patch.js +3 -17
- package/dist/private-invocation-computer.d.ts +224 -12
- package/dist/private-invocation-computer.js +162 -22
- package/dist/private-invocation-files.d.ts +3 -2
- package/dist/private-invocation-files.js +22 -3
- package/dist/private-local-agent-control.d.ts +2 -2
- package/dist/private-local-bwrap.d.ts +9 -7
- package/dist/private-local-bwrap.js +26 -21
- package/dist/private-local-computer-processes.d.ts +15 -0
- package/dist/private-local-computer-processes.js +13 -0
- package/dist/private-local-containment.d.ts +15 -13
- package/dist/private-local-containment.js +49 -65
- package/dist/private-local-delegation.d.ts +3 -3
- package/dist/private-local-delegation.js +32 -19
- package/dist/private-local-exec-review.d.ts +12 -0
- package/dist/private-local-exec-review.js +62 -0
- package/dist/private-local-exec-rules.d.ts +16 -0
- package/dist/private-local-exec-rules.js +441 -0
- package/dist/private-local-git-exclude.js +25 -15
- package/dist/private-local-linux-sandbox.d.ts +5 -5
- package/dist/private-local-linux-sandbox.js +63 -30
- package/dist/private-local-mail.d.ts +2 -2
- package/dist/private-local-mesh-links.d.ts +4 -1
- package/dist/private-local-mesh-links.js +50 -3
- package/dist/private-local-native-digests.js +1 -1
- package/dist/private-local-native-install-error.d.ts +2 -0
- package/dist/private-local-native-install-error.js +10 -0
- package/dist/private-local-native-staging.d.ts +7 -8
- package/dist/private-local-native-staging.js +132 -43
- package/dist/private-local-permission-mode.d.ts +10 -0
- package/dist/private-local-permission-mode.js +18 -0
- package/dist/private-local-runtime-host.d.ts +28 -5
- package/dist/private-local-runtime-host.js +404 -66
- package/dist/private-local-sandbox-policy.d.ts +30 -11
- package/dist/private-local-sandbox-policy.js +223 -373
- package/dist/private-local-sandbox.d.ts +30 -25
- package/dist/private-local-sandbox.js +190 -100
- package/dist/private-local-seatbelt.d.ts +7 -13
- package/dist/private-local-seatbelt.js +55 -32
- package/dist/private-local-service-contract.d.ts +5 -4
- package/dist/private-local-service-discovery.d.ts +19 -7
- package/dist/private-local-service-discovery.js +4 -3
- package/dist/private-local-service-listener.js +40 -2
- package/dist/private-local-service-log.d.ts +18 -0
- package/dist/private-local-service-log.js +6 -0
- package/dist/private-local-service-owner.d.ts +18 -10
- package/dist/private-local-service-owner.js +119 -25
- package/dist/private-local-service.js +30 -3
- package/dist/private-local-session-discovery.d.ts +2 -1
- package/dist/private-local-session-discovery.js +18 -6
- package/dist/private-mcp-form-contract.d.ts +10 -1
- package/dist/private-mcp-form-contract.js +10 -1
- package/dist/private-mcp-form-session.d.ts +39 -3
- package/dist/private-mcp-form-session.js +28 -5
- package/dist/private-oyster-mcp-auth.d.ts +1 -1
- package/dist/private-oyster-mcp-auth.js +139 -7
- package/dist/private-provider-rejection.js +1 -1
- package/dist/seatbelt/safe-base.sbpl +2 -2
- package/native/bin/bwrap-build-source/Dockerfile +2 -0
- package/native/bin/bwrap-build-source/README.md +6 -6
- package/native/bin/bwrap-build-source/pin.mjs +2 -2
- package/native/bin/bwrap-build-source/record.mjs +12 -1
- package/native/bin/bwrap-build-source/vendor/seccomp-unix-block.c +10 -7
- package/native/bin/bwrap-build.json +3 -2
- package/native/bin/fungi-apply-patch +0 -0
- package/native/bin/fungi-seccomp-workspace.bpf +0 -0
- package/native/bin/fungi-seccomp-workspace.bpf.sha256 +1 -0
- package/package.json +23 -17
|
@@ -1,24 +1,10 @@
|
|
|
1
1
|
import { access, constants } from "node:fs/promises";
|
|
2
|
-
import { fileURLToPath } from "node:url";
|
|
3
|
-
import { createRequire } from "node:module";
|
|
4
2
|
import process from "node:process";
|
|
5
3
|
import * as ChildProcess from "effect/process/ChildProcess";
|
|
6
4
|
import { parsePatch } from "diff";
|
|
7
5
|
import * as Stream from "effect/Stream";
|
|
8
6
|
import { runPrivateInvocationProcess, } from "./private-invocation-computer.js";
|
|
9
|
-
import { createNativePayloadStaging } from "./private-local-native-staging.js";
|
|
10
|
-
function patchExecutable() {
|
|
11
|
-
if (process.env.FUNGI_BUNDLED_CLI) {
|
|
12
|
-
const name = `@fungi.computer/spore-native-${process.platform}-${process.arch}`;
|
|
13
|
-
try {
|
|
14
|
-
return createRequire(process.env.FUNGI_BUNDLED_CLI).resolve(`${name}/bin/fungi-apply-patch`);
|
|
15
|
-
}
|
|
16
|
-
catch (cause) {
|
|
17
|
-
throw new Error(`Missing ${name}; reinstall Fungi with npm optional dependencies enabled`, { cause });
|
|
18
|
-
}
|
|
19
|
-
}
|
|
20
|
-
return fileURLToPath(new URL("../native/bin/fungi-apply-patch", import.meta.url));
|
|
21
|
-
}
|
|
7
|
+
import { createNativePayloadStaging, packagedPatchPath, } from "./private-local-native-staging.js";
|
|
22
8
|
const maximumPatchBytes = 128 * 1024;
|
|
23
9
|
const mismatchExitCode = 23;
|
|
24
10
|
const formatExitCode = 24;
|
|
@@ -50,7 +36,7 @@ async function unifiedDiffContext(patch, read, context) {
|
|
|
50
36
|
}
|
|
51
37
|
/** Advertise only a packaged native engine; never install tools during a model call. */
|
|
52
38
|
export async function createPrivateFilesPatch(root, read, sandbox) {
|
|
53
|
-
const executable =
|
|
39
|
+
const executable = packagedPatchPath();
|
|
54
40
|
try {
|
|
55
41
|
// Advertise the installed helper if present; only the verified private
|
|
56
42
|
// copy is executed, so its source does not need an execute bit.
|
|
@@ -78,7 +64,7 @@ export async function createPrivateFilesPatch(root, read, sandbox) {
|
|
|
78
64
|
if (executableToRun === undefined)
|
|
79
65
|
throw new Error("Native Files helper was not staged");
|
|
80
66
|
const env = { ...process.env, ...launch?.environment };
|
|
81
|
-
if (launch !== undefined
|
|
67
|
+
if (launch !== undefined) {
|
|
82
68
|
delete env.XDG_RUNTIME_DIR;
|
|
83
69
|
delete env.DBUS_SESSION_BUS_ADDRESS;
|
|
84
70
|
}
|
|
@@ -1,17 +1,72 @@
|
|
|
1
1
|
import * as ChildProcess from "effect/process/ChildProcess";
|
|
2
2
|
import type { KnapsackComputer } from "@fungi.computer/knapsack";
|
|
3
|
+
import { type InvocationContext } from "@fungi.computer/mycelium";
|
|
3
4
|
import { z } from "zod";
|
|
4
|
-
import { type PrivateLocalProcessScope } from "./private-local-computer-processes.js";
|
|
5
|
+
import { backgroundExecInput, backgroundExecOutput, type PrivateLocalProcessScope } from "./private-local-computer-processes.js";
|
|
5
6
|
import type { SandboxCommand } from "./private-local-containment.js";
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
/**
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
7
|
+
import type { LocalPermissionMode } from "./private-local-permission-mode.js";
|
|
8
|
+
import type { PendingApproval, ApprovalRefusal } from "@fungi.computer/approvals";
|
|
9
|
+
/** A Full-access launch is bare; sandboxed launches always supply a command or refuse. */
|
|
10
|
+
export type LocalSandboxLaunch = (cwd: string, argv: readonly [string, ...string[]], readOnlyHelperFile?: string) => SandboxCommand | undefined;
|
|
11
|
+
/**
|
|
12
|
+
* `command` (shell) and `executable`/`args` (direct) are mutually exclusive:
|
|
13
|
+
* each branch is a strict object, so supplying both or neither fails every
|
|
14
|
+
* branch and the union is refused.
|
|
15
|
+
*/
|
|
16
|
+
declare const execInput: z.ZodUnion<readonly [z.ZodObject<{
|
|
17
|
+
command: z.ZodString;
|
|
18
|
+
stdin: z.ZodOptional<z.ZodString>;
|
|
19
|
+
operation: z.ZodDefault<z.ZodLiteral<"run">>;
|
|
20
|
+
escape: z.ZodOptional<z.ZodObject<{
|
|
21
|
+
justification: z.ZodString;
|
|
22
|
+
}, z.core.$strict>>;
|
|
23
|
+
cwd: z.ZodOptional<z.ZodString>;
|
|
24
|
+
timeoutMs: z.ZodDefault<z.ZodNumber>;
|
|
25
|
+
}, z.core.$strict>, z.ZodObject<{
|
|
26
|
+
executable: z.ZodString;
|
|
27
|
+
args: z.ZodDefault<z.ZodArray<z.ZodString>>;
|
|
28
|
+
stdin: z.ZodOptional<z.ZodString>;
|
|
29
|
+
operation: z.ZodDefault<z.ZodLiteral<"run">>;
|
|
30
|
+
escape: z.ZodOptional<z.ZodObject<{
|
|
31
|
+
justification: z.ZodString;
|
|
32
|
+
}, z.core.$strict>>;
|
|
33
|
+
cwd: z.ZodOptional<z.ZodString>;
|
|
34
|
+
timeoutMs: z.ZodDefault<z.ZodNumber>;
|
|
35
|
+
}, z.core.$strict>]>;
|
|
36
|
+
declare const completedOutput: z.ZodObject<{
|
|
37
|
+
exitCode: z.ZodNumber;
|
|
38
|
+
stdout: z.ZodString;
|
|
39
|
+
stderr: z.ZodString;
|
|
40
|
+
truncated: z.ZodUnion<readonly [z.ZodLiteral<false>, z.ZodArray<z.ZodObject<{
|
|
41
|
+
stream: z.ZodEnum<{
|
|
42
|
+
stdout: "stdout";
|
|
43
|
+
stderr: "stderr";
|
|
44
|
+
}>;
|
|
45
|
+
keptBytes: z.ZodNumber;
|
|
46
|
+
totalBytes: z.ZodNumber;
|
|
47
|
+
}, z.core.$strict>>]>;
|
|
48
|
+
status: z.ZodEnum<{
|
|
49
|
+
failed: "failed";
|
|
50
|
+
completed: "completed";
|
|
51
|
+
}>;
|
|
52
|
+
}, z.core.$strict>;
|
|
53
|
+
declare const deadlineOutput: z.ZodObject<{
|
|
54
|
+
exitCode: z.ZodNumber;
|
|
55
|
+
stdout: z.ZodString;
|
|
56
|
+
stderr: z.ZodString;
|
|
57
|
+
truncated: z.ZodUnion<readonly [z.ZodLiteral<false>, z.ZodArray<z.ZodObject<{
|
|
58
|
+
stream: z.ZodEnum<{
|
|
59
|
+
stdout: "stdout";
|
|
60
|
+
stderr: "stderr";
|
|
61
|
+
}>;
|
|
62
|
+
keptBytes: z.ZodNumber;
|
|
63
|
+
totalBytes: z.ZodNumber;
|
|
64
|
+
}, z.core.$strict>>]>;
|
|
65
|
+
status: z.ZodLiteral<"deadline_exceeded">;
|
|
66
|
+
timeoutMs: z.ZodNumber;
|
|
67
|
+
elapsedMs: z.ZodNumber;
|
|
68
|
+
message: z.ZodString;
|
|
69
|
+
}, z.core.$strict>;
|
|
15
70
|
declare const execOutput: z.ZodDiscriminatedUnion<[z.ZodObject<{
|
|
16
71
|
exitCode: z.ZodNumber;
|
|
17
72
|
stdout: z.ZodString;
|
|
@@ -28,6 +83,15 @@ declare const execOutput: z.ZodDiscriminatedUnion<[z.ZodObject<{
|
|
|
28
83
|
failed: "failed";
|
|
29
84
|
completed: "completed";
|
|
30
85
|
}>;
|
|
86
|
+
sandbox: z.ZodObject<{
|
|
87
|
+
mode: z.ZodEnum<{
|
|
88
|
+
"read-only": "read-only";
|
|
89
|
+
workspace: "workspace";
|
|
90
|
+
"full-access": "full-access";
|
|
91
|
+
}>;
|
|
92
|
+
likelyDenied: z.ZodBoolean;
|
|
93
|
+
ranOutsideSandbox: z.ZodDefault<z.ZodBoolean>;
|
|
94
|
+
}, z.core.$strict>;
|
|
31
95
|
}, z.core.$strict>, z.ZodObject<{
|
|
32
96
|
exitCode: z.ZodNumber;
|
|
33
97
|
stdout: z.ZodString;
|
|
@@ -44,15 +108,161 @@ declare const execOutput: z.ZodDiscriminatedUnion<[z.ZodObject<{
|
|
|
44
108
|
timeoutMs: z.ZodNumber;
|
|
45
109
|
elapsedMs: z.ZodNumber;
|
|
46
110
|
message: z.ZodString;
|
|
111
|
+
sandbox: z.ZodObject<{
|
|
112
|
+
mode: z.ZodEnum<{
|
|
113
|
+
"read-only": "read-only";
|
|
114
|
+
workspace: "workspace";
|
|
115
|
+
"full-access": "full-access";
|
|
116
|
+
}>;
|
|
117
|
+
likelyDenied: z.ZodBoolean;
|
|
118
|
+
ranOutsideSandbox: z.ZodDefault<z.ZodBoolean>;
|
|
119
|
+
}, z.core.$strict>;
|
|
47
120
|
}, z.core.$strict>], "status">;
|
|
121
|
+
export declare const shellOutput: z.ZodUnion<readonly [z.ZodDiscriminatedUnion<[z.ZodObject<{
|
|
122
|
+
exitCode: z.ZodNumber;
|
|
123
|
+
stdout: z.ZodString;
|
|
124
|
+
stderr: z.ZodString;
|
|
125
|
+
truncated: z.ZodUnion<readonly [z.ZodLiteral<false>, z.ZodArray<z.ZodObject<{
|
|
126
|
+
stream: z.ZodEnum<{
|
|
127
|
+
stdout: "stdout";
|
|
128
|
+
stderr: "stderr";
|
|
129
|
+
}>;
|
|
130
|
+
keptBytes: z.ZodNumber;
|
|
131
|
+
totalBytes: z.ZodNumber;
|
|
132
|
+
}, z.core.$strict>>]>;
|
|
133
|
+
status: z.ZodEnum<{
|
|
134
|
+
failed: "failed";
|
|
135
|
+
completed: "completed";
|
|
136
|
+
}>;
|
|
137
|
+
sandbox: z.ZodObject<{
|
|
138
|
+
mode: z.ZodEnum<{
|
|
139
|
+
"read-only": "read-only";
|
|
140
|
+
workspace: "workspace";
|
|
141
|
+
"full-access": "full-access";
|
|
142
|
+
}>;
|
|
143
|
+
likelyDenied: z.ZodBoolean;
|
|
144
|
+
ranOutsideSandbox: z.ZodDefault<z.ZodBoolean>;
|
|
145
|
+
}, z.core.$strict>;
|
|
146
|
+
}, z.core.$strict>, z.ZodObject<{
|
|
147
|
+
exitCode: z.ZodNumber;
|
|
148
|
+
stdout: z.ZodString;
|
|
149
|
+
stderr: z.ZodString;
|
|
150
|
+
truncated: z.ZodUnion<readonly [z.ZodLiteral<false>, z.ZodArray<z.ZodObject<{
|
|
151
|
+
stream: z.ZodEnum<{
|
|
152
|
+
stdout: "stdout";
|
|
153
|
+
stderr: "stderr";
|
|
154
|
+
}>;
|
|
155
|
+
keptBytes: z.ZodNumber;
|
|
156
|
+
totalBytes: z.ZodNumber;
|
|
157
|
+
}, z.core.$strict>>]>;
|
|
158
|
+
status: z.ZodLiteral<"deadline_exceeded">;
|
|
159
|
+
timeoutMs: z.ZodNumber;
|
|
160
|
+
elapsedMs: z.ZodNumber;
|
|
161
|
+
message: z.ZodString;
|
|
162
|
+
sandbox: z.ZodObject<{
|
|
163
|
+
mode: z.ZodEnum<{
|
|
164
|
+
"read-only": "read-only";
|
|
165
|
+
workspace: "workspace";
|
|
166
|
+
"full-access": "full-access";
|
|
167
|
+
}>;
|
|
168
|
+
likelyDenied: z.ZodBoolean;
|
|
169
|
+
ranOutsideSandbox: z.ZodDefault<z.ZodBoolean>;
|
|
170
|
+
}, z.core.$strict>;
|
|
171
|
+
}, z.core.$strict>], "status">, z.ZodUnion<readonly [z.ZodObject<{
|
|
172
|
+
process: z.ZodObject<{
|
|
173
|
+
id: z.ZodString & z.ZodType<import("@fungi.computer/cairn").CairnId<"local-computer-process", "process", 1>, string, z.core.$ZodTypeInternals<import("@fungi.computer/cairn").CairnId<"local-computer-process", "process", 1>, string>>;
|
|
174
|
+
command: z.ZodString;
|
|
175
|
+
cwd: z.ZodString;
|
|
176
|
+
state: z.ZodEnum<{
|
|
177
|
+
running: "running";
|
|
178
|
+
exited: "exited";
|
|
179
|
+
}>;
|
|
180
|
+
exitCode: z.ZodNullable<z.ZodNumber>;
|
|
181
|
+
signal: z.ZodNullable<z.ZodString>;
|
|
182
|
+
startedAt: z.ZodNumber;
|
|
183
|
+
endedAt: z.ZodNullable<z.ZodNumber>;
|
|
184
|
+
logPath: z.ZodString;
|
|
185
|
+
logBytes: z.ZodNumber;
|
|
186
|
+
logStartOffset: z.ZodNumber;
|
|
187
|
+
logTruncated: z.ZodBoolean;
|
|
188
|
+
}, z.core.$strict>;
|
|
189
|
+
offset: z.ZodNumber;
|
|
190
|
+
nextOffset: z.ZodNumber;
|
|
191
|
+
text: z.ZodString;
|
|
192
|
+
complete: z.ZodBoolean;
|
|
193
|
+
timedOut: z.ZodBoolean;
|
|
194
|
+
signalled: z.ZodOptional<z.ZodBoolean>;
|
|
195
|
+
sandbox: z.ZodOptional<z.ZodObject<{
|
|
196
|
+
mode: z.ZodEnum<{
|
|
197
|
+
"read-only": "read-only";
|
|
198
|
+
workspace: "workspace";
|
|
199
|
+
"full-access": "full-access";
|
|
200
|
+
}>;
|
|
201
|
+
likelyDenied: z.ZodBoolean;
|
|
202
|
+
ranOutsideSandbox: z.ZodBoolean;
|
|
203
|
+
}, z.core.$strict>>;
|
|
204
|
+
}, z.core.$strict>, z.ZodObject<{
|
|
205
|
+
processes: z.ZodArray<z.ZodObject<{
|
|
206
|
+
id: z.ZodString & z.ZodType<import("@fungi.computer/cairn").CairnId<"local-computer-process", "process", 1>, string, z.core.$ZodTypeInternals<import("@fungi.computer/cairn").CairnId<"local-computer-process", "process", 1>, string>>;
|
|
207
|
+
command: z.ZodString;
|
|
208
|
+
cwd: z.ZodString;
|
|
209
|
+
state: z.ZodEnum<{
|
|
210
|
+
running: "running";
|
|
211
|
+
exited: "exited";
|
|
212
|
+
}>;
|
|
213
|
+
exitCode: z.ZodNullable<z.ZodNumber>;
|
|
214
|
+
signal: z.ZodNullable<z.ZodString>;
|
|
215
|
+
startedAt: z.ZodNumber;
|
|
216
|
+
endedAt: z.ZodNullable<z.ZodNumber>;
|
|
217
|
+
logPath: z.ZodString;
|
|
218
|
+
logBytes: z.ZodNumber;
|
|
219
|
+
logStartOffset: z.ZodNumber;
|
|
220
|
+
logTruncated: z.ZodBoolean;
|
|
221
|
+
}, z.core.$strict>>;
|
|
222
|
+
}, z.core.$strict>]>, z.ZodUnion<readonly [z.ZodObject<{
|
|
223
|
+
kind: z.ZodLiteral<"pending_approval">;
|
|
224
|
+
recordId: z.ZodString;
|
|
225
|
+
description: z.ZodObject<{
|
|
226
|
+
title: z.ZodString;
|
|
227
|
+
inputBytes: z.ZodNumber;
|
|
228
|
+
inputSha256: z.ZodString;
|
|
229
|
+
}, z.core.$strict>;
|
|
230
|
+
}, z.core.$strict>, z.ZodObject<{
|
|
231
|
+
kind: z.ZodLiteral<"refused">;
|
|
232
|
+
reason: z.ZodString;
|
|
233
|
+
}, z.core.$strict>]>]>;
|
|
234
|
+
/** A host-attested stage door; the Computer cannot mint approval identity itself. */
|
|
235
|
+
export type LocalExecEscapeStage = (input: Readonly<{
|
|
236
|
+
request: z.output<typeof execInput> | Extract<z.output<typeof backgroundExecInput>, {
|
|
237
|
+
operation: "start";
|
|
238
|
+
}>;
|
|
239
|
+
cwd: string;
|
|
240
|
+
mode: Exclude<LocalPermissionMode, "full-access">;
|
|
241
|
+
denialLine: string | null;
|
|
242
|
+
context: InvocationContext;
|
|
243
|
+
executionPath: string;
|
|
244
|
+
}>) => Promise<PendingApproval | ApprovalRefusal>;
|
|
245
|
+
/** Host-only prefix policy: the model has no rule-management door. */
|
|
246
|
+
export type LocalExecPolicyRun = (input: Parameters<LocalExecEscapeStage>[0]) => Promise<z.output<typeof shellOutput> | undefined>;
|
|
247
|
+
/** Run the recorded command through the normal bounded process door, without a sandbox.
|
|
248
|
+
* The host supplies the captured PATH and canonical cwd; the record supplies the
|
|
249
|
+
* exact original request. No model invocation can call this door directly. */
|
|
250
|
+
export declare function dispatchApprovedLocalExec(input: unknown, cwd: string, executionPath: string, home: string, processes: PrivateLocalProcessScope, mode: LocalPermissionMode): Promise<ExecOutput | z.output<typeof backgroundExecOutput>>;
|
|
48
251
|
export type ExecOutput = z.output<typeof execOutput>;
|
|
252
|
+
export type ProcessOutput = z.output<typeof completedOutput> | z.output<typeof deadlineOutput>;
|
|
253
|
+
/**
|
|
254
|
+
* A hint to the model, not evidence that an operation was refused. Only the
|
|
255
|
+
* sandboxed launch can carry this hint; common shell usage errors and missing
|
|
256
|
+
* commands are not sandbox denials, even if their stderr mentions permissions.
|
|
257
|
+
*/
|
|
258
|
+
export declare function likelySandboxDenied(mode: LocalPermissionMode, result: ProcessOutput): boolean;
|
|
49
259
|
/** Shared bounded native process owner; callers supply argv/stdin, never shell interpolation. */
|
|
50
260
|
export declare function runPrivateInvocationProcess(processCommand: ChildProcess.Command, signal: AbortSignal, timeoutMs: number | undefined, describe?: Readonly<{
|
|
51
261
|
/** The timeout actually requested, before any clamp; defaults to `timeoutMs`. */
|
|
52
262
|
requestedMs?: number;
|
|
53
263
|
/** The real command text, for the timeout message; omitted when not meaningful (e.g. a native helper). */
|
|
54
264
|
describeCommand?: () => string;
|
|
55
|
-
}>): Promise<
|
|
265
|
+
}>): Promise<ProcessOutput>;
|
|
56
266
|
/**
|
|
57
267
|
* Construct one private invocation-root Computer: bounded shell exec, plus the
|
|
58
268
|
* background-process operations of `processes` when the host supplies a scope.
|
|
@@ -61,5 +271,7 @@ export declare function createPrivateInvocationComputer(invocationRoot: string,
|
|
|
61
271
|
/** Selected host home; standalone callers capture it before any await. */
|
|
62
272
|
home?: string,
|
|
63
273
|
/** Native Local always supplies its live per-Agent admission callback. */
|
|
64
|
-
sandbox?: LocalSandboxLaunch
|
|
274
|
+
sandbox?: LocalSandboxLaunch,
|
|
275
|
+
/** The live Agent mode, captured once at the command boundary. */
|
|
276
|
+
currentMode?: () => LocalPermissionMode, stageEscape?: LocalExecEscapeStage, runPolicy?: LocalExecPolicyRun): Promise<KnapsackComputer>;
|
|
65
277
|
export {};
|
|
@@ -46,6 +46,9 @@ const capabilityNameSchema = z
|
|
|
46
46
|
.regex(/^[A-Za-z][A-Za-z0-9_]*$/);
|
|
47
47
|
const runCommonFields = {
|
|
48
48
|
operation: z.literal("run").default("run"),
|
|
49
|
+
escape: z
|
|
50
|
+
.strictObject({ justification: z.string().min(1).max(2048) })
|
|
51
|
+
.optional(),
|
|
49
52
|
cwd: launchCwdSchema.optional(),
|
|
50
53
|
// Accepted up to the full invocation deadline and silently clamped to
|
|
51
54
|
// maximumCommandTimeoutMs (see runCommandAtInvocationBoundary); rejecting
|
|
@@ -93,21 +96,68 @@ const commandOutputFields = {
|
|
|
93
96
|
stderr: z.string(),
|
|
94
97
|
truncated: z.union([z.literal(false), z.array(execTruncation).min(1)]),
|
|
95
98
|
};
|
|
99
|
+
const sandboxResult = z.strictObject({
|
|
100
|
+
mode: z.enum(["full-access", "workspace", "read-only"]),
|
|
101
|
+
likelyDenied: z.boolean(),
|
|
102
|
+
ranOutsideSandbox: z.boolean().default(false),
|
|
103
|
+
});
|
|
104
|
+
const completedOutput = z.strictObject({
|
|
105
|
+
status: z.enum(["completed", "failed"]),
|
|
106
|
+
...commandOutputFields,
|
|
107
|
+
});
|
|
108
|
+
const deadlineOutput = z.strictObject({
|
|
109
|
+
status: z.literal("deadline_exceeded"),
|
|
110
|
+
timeoutMs: z.number().int().positive(),
|
|
111
|
+
elapsedMs: z.number().nonnegative(),
|
|
112
|
+
message: z.string(),
|
|
113
|
+
...commandOutputFields,
|
|
114
|
+
});
|
|
96
115
|
const execOutput = z.discriminatedUnion("status", [
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
...commandOutputFields,
|
|
100
|
-
}),
|
|
101
|
-
z.strictObject({
|
|
102
|
-
status: z.literal("deadline_exceeded"),
|
|
103
|
-
timeoutMs: z.number().int().positive(),
|
|
104
|
-
elapsedMs: z.number().nonnegative(),
|
|
105
|
-
message: z.string(),
|
|
106
|
-
...commandOutputFields,
|
|
107
|
-
}),
|
|
116
|
+
completedOutput.extend({ sandbox: sandboxResult }),
|
|
117
|
+
deadlineOutput.extend({ sandbox: sandboxResult }),
|
|
108
118
|
]);
|
|
109
119
|
const shellInput = z.union([execInput, backgroundExecInput]);
|
|
110
|
-
const
|
|
120
|
+
const pendingOutput = z.strictObject({
|
|
121
|
+
kind: z.literal("pending_approval"),
|
|
122
|
+
recordId: z.string(),
|
|
123
|
+
description: z.strictObject({
|
|
124
|
+
title: z.string(),
|
|
125
|
+
inputBytes: z.number(),
|
|
126
|
+
inputSha256: z.string(),
|
|
127
|
+
}),
|
|
128
|
+
});
|
|
129
|
+
const refusalOutput = z.strictObject({
|
|
130
|
+
kind: z.literal("refused"),
|
|
131
|
+
reason: z.string(),
|
|
132
|
+
});
|
|
133
|
+
const approvalOutput = z.union([pendingOutput, refusalOutput]);
|
|
134
|
+
export const shellOutput = z.union([
|
|
135
|
+
execOutput,
|
|
136
|
+
backgroundExecOutput,
|
|
137
|
+
approvalOutput,
|
|
138
|
+
]);
|
|
139
|
+
/** Run the recorded command through the normal bounded process door, without a sandbox.
|
|
140
|
+
* The host supplies the captured PATH and canonical cwd; the record supplies the
|
|
141
|
+
* exact original request. No model invocation can call this door directly. */
|
|
142
|
+
export async function dispatchApprovedLocalExec(input, cwd, executionPath, home, processes, mode) {
|
|
143
|
+
const request = shellInput.parse(input);
|
|
144
|
+
if (request.operation === "run") {
|
|
145
|
+
const result = await runCommandAtInvocationBoundary(request, cwd, executionPath, home, new AbortController().signal);
|
|
146
|
+
return execOutput.parse({
|
|
147
|
+
...result,
|
|
148
|
+
sandbox: { mode, likelyDenied: false, ranOutsideSandbox: true },
|
|
149
|
+
});
|
|
150
|
+
}
|
|
151
|
+
if (request.operation !== "start")
|
|
152
|
+
throw new Error("Only recorded command launches may be dispatched");
|
|
153
|
+
const output = await callBackgroundExec(processes, request, () => Promise.resolve(cwd), executionPath, new AbortController().signal);
|
|
154
|
+
return "text" in output
|
|
155
|
+
? {
|
|
156
|
+
...output,
|
|
157
|
+
sandbox: { mode, likelyDenied: false, ranOutsideSandbox: true },
|
|
158
|
+
}
|
|
159
|
+
: output;
|
|
160
|
+
}
|
|
111
161
|
function computerCapabilities(root, processes) {
|
|
112
162
|
return jsonValueSchema.parse([
|
|
113
163
|
{
|
|
@@ -116,9 +166,11 @@ function computerCapabilities(root, processes) {
|
|
|
116
166
|
kind: "runtime",
|
|
117
167
|
// A filesystem path has no place in the bounded discovery summary.
|
|
118
168
|
// Publish the exact root in the input schema, without truncating it.
|
|
119
|
-
description: `Shell, terminal, git, builds. cwd uses the root in the input schema. ~/$HOME use home. Or {executable,args}; stdin once, closed. exec kills its group at call end; nohup/& die. timeoutMs default ${String(defaultCommandTimeoutMs)}, max ${String(maximumCommandTimeoutMs)} ms; invocation ${String(invocationDeadlineMs)} ms; deadline returns partial output. Streams keep 64 KiB, then a [stdout|stderr truncated] line; redirect to a file or head/tail.${processes === undefined ? "" : ' Jobs: {operation:"start",command} or executable+args returns process.id; poll/wait: state, exitCode, tail. wait <=60000 ms; timedOut: wait expired. Cancelled wait keeps jobs. maxBytes <=64 KiB, offset/nextOffset; keeps last 8 MiB (logStartOffset). kill stops group; list: Session jobs. Delete/close stop jobs. Use wait, not sleep.'}`,
|
|
169
|
+
description: `Shell, terminal, git, builds. cwd uses the root in the input schema. ~/$HOME use home. Or {executable,args}; stdin once, closed. exec kills its group at call end; nohup/& die. timeoutMs default ${String(defaultCommandTimeoutMs)}, max ${String(maximumCommandTimeoutMs)} ms; invocation ${String(invocationDeadlineMs)} ms; deadline returns partial output. Streams keep 64 KiB, then a [stdout|stderr truncated] line; redirect to a file or head/tail. Only after a sandbox denial, retry with escape: {justification} to request human approval; no command runs until approval.${processes === undefined ? "" : ' Jobs: {operation:"start",command} or executable+args returns process.id; poll/wait: state, exitCode, tail. wait <=60000 ms; timedOut: wait expired. Cancelled wait keeps jobs. maxBytes <=64 KiB, offset/nextOffset; keeps last 8 MiB (logStartOffset). kill stops group; list: Session jobs. Delete/close stop jobs. Use wait, not sleep.'}`,
|
|
120
170
|
parameters: structuredClone(z.toJSONSchema((processes === undefined ? execInput : shellInput).describe(`Computer root: ${root}. cwd must be absolute and remain inside this root; omission starts at the root.`), { io: "input" })),
|
|
121
|
-
output: structuredClone(z.toJSONSchema(processes === undefined
|
|
171
|
+
output: structuredClone(z.toJSONSchema(processes === undefined
|
|
172
|
+
? z.union([execOutput, approvalOutput])
|
|
173
|
+
: shellOutput)),
|
|
122
174
|
},
|
|
123
175
|
]);
|
|
124
176
|
}
|
|
@@ -134,6 +186,24 @@ function commandTimeoutMessage(input) {
|
|
|
134
186
|
: "";
|
|
135
187
|
return `Command exceeded timeoutMs=${String(input.timeoutMs)} ms${clampNote}. Command: ${shortCommandLabel(input.command)}. Rerun with a larger timeoutMs, up to ${String(maximumCommandTimeoutMs)} ms; split longer work into separate tool calls.`;
|
|
136
188
|
}
|
|
189
|
+
/** Process stderr has no structured errno; classify only this display hint. */
|
|
190
|
+
function classifySandboxDiagnostic(diagnostic) {
|
|
191
|
+
return /\b(?:EROFS|EPERM|EBUSY|read-only file system|operation not permitted|permission denied|device or resource busy|seccomp|sandbox|landlock)\b/iu.test(diagnostic)
|
|
192
|
+
? "denial"
|
|
193
|
+
: "other";
|
|
194
|
+
}
|
|
195
|
+
/**
|
|
196
|
+
* A hint to the model, not evidence that an operation was refused. Only the
|
|
197
|
+
* sandboxed launch can carry this hint; common shell usage errors and missing
|
|
198
|
+
* commands are not sandbox denials, even if their stderr mentions permissions.
|
|
199
|
+
*/
|
|
200
|
+
export function likelySandboxDenied(mode, result) {
|
|
201
|
+
if (mode === "full-access" ||
|
|
202
|
+
result.exitCode === 0 ||
|
|
203
|
+
[2, 126, 127].includes(result.exitCode))
|
|
204
|
+
return false;
|
|
205
|
+
return classifySandboxDiagnostic(result.stderr) === "denial";
|
|
206
|
+
}
|
|
137
207
|
function isErrnoException(error) {
|
|
138
208
|
return "code" in error;
|
|
139
209
|
}
|
|
@@ -348,7 +418,7 @@ function buildRunCommand(request, cwd, executionPath, home, sandbox) {
|
|
|
348
418
|
? ["sh", "-c", request.command]
|
|
349
419
|
: [request.executable, ...request.args];
|
|
350
420
|
// Admission happens before spawn and is never retried after an executable
|
|
351
|
-
// may have run. Folder validity and
|
|
421
|
+
// may have run. Folder validity and mode belong to the host.
|
|
352
422
|
const launch = sandbox?.(cwd, argv);
|
|
353
423
|
if (launch !== undefined) {
|
|
354
424
|
delete env.XDG_RUNTIME_DIR;
|
|
@@ -445,11 +515,19 @@ export function runPrivateInvocationProcess(processCommand, signal, timeoutMs, d
|
|
|
445
515
|
};
|
|
446
516
|
})).pipe(Effect.provide(NodeServices.layer)), { signal });
|
|
447
517
|
}
|
|
448
|
-
async function execute(root, executionPath, home, request, context, sandbox) {
|
|
518
|
+
async function execute(root, executionPath, home, request, context, sandbox, mode = "full-access") {
|
|
449
519
|
context.signal.throwIfAborted();
|
|
450
520
|
const cwd = await resolveCwd(root, request.cwd);
|
|
451
521
|
context.signal.throwIfAborted();
|
|
452
|
-
|
|
522
|
+
const result = await runCommandAtInvocationBoundary(request, cwd, executionPath, home, context.signal, sandbox);
|
|
523
|
+
return execOutput.parse({
|
|
524
|
+
...result,
|
|
525
|
+
sandbox: {
|
|
526
|
+
mode,
|
|
527
|
+
likelyDenied: likelySandboxDenied(mode, result),
|
|
528
|
+
ranOutsideSandbox: sandbox === undefined,
|
|
529
|
+
},
|
|
530
|
+
});
|
|
453
531
|
}
|
|
454
532
|
/**
|
|
455
533
|
* Construct one private invocation-root Computer: bounded shell exec, plus the
|
|
@@ -459,9 +537,12 @@ export async function createPrivateInvocationComputer(invocationRoot, executionP
|
|
|
459
537
|
/** Selected host home; standalone callers capture it before any await. */
|
|
460
538
|
home = capturePrivateLocalConfigRoot().home,
|
|
461
539
|
/** Native Local always supplies its live per-Agent admission callback. */
|
|
462
|
-
sandbox
|
|
540
|
+
sandbox,
|
|
541
|
+
/** The live Agent mode, captured once at the command boundary. */
|
|
542
|
+
currentMode = () => "full-access", stageEscape, runPolicy) {
|
|
463
543
|
const path = parsePrivateLocalExecPath(executionPath);
|
|
464
544
|
const root = await realpath(resolve(invocationRoot));
|
|
545
|
+
let lastDenialLine = null;
|
|
465
546
|
return Object.freeze({
|
|
466
547
|
getCapabilities: () => Promise.resolve(computerCapabilities(root, processes)),
|
|
467
548
|
call: async (capabilityId, input, context) => {
|
|
@@ -488,10 +569,69 @@ sandbox) {
|
|
|
488
569
|
message: "Computer exec command or argument contains a NUL byte. Remove it or double the backslash in JavaScript escapes (for example, \\\\x00); no process was started.",
|
|
489
570
|
},
|
|
490
571
|
});
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
572
|
+
const mode = currentMode();
|
|
573
|
+
if (mode !== "full-access" &&
|
|
574
|
+
(request.operation === "run" || request.operation === "start") &&
|
|
575
|
+
"executable" in request &&
|
|
576
|
+
runPolicy !== undefined) {
|
|
577
|
+
const cwd = await resolveCwd(root, request.cwd);
|
|
578
|
+
context.signal.throwIfAborted();
|
|
579
|
+
const allowed = await runPolicy({
|
|
580
|
+
request,
|
|
581
|
+
cwd,
|
|
582
|
+
mode,
|
|
583
|
+
denialLine: null,
|
|
584
|
+
context,
|
|
585
|
+
executionPath: path,
|
|
586
|
+
});
|
|
587
|
+
if (allowed !== undefined)
|
|
588
|
+
return jsonValueSchema.parse(allowed);
|
|
589
|
+
}
|
|
590
|
+
if ((request.operation === "run" || request.operation === "start") &&
|
|
591
|
+
request.escape !== undefined &&
|
|
592
|
+
mode !== "full-access") {
|
|
593
|
+
if (stageEscape === undefined)
|
|
594
|
+
throw new Error("Local escape approval owner is unavailable; command was not run");
|
|
595
|
+
const cwd = await resolveCwd(root, request.cwd);
|
|
596
|
+
context.signal.throwIfAborted();
|
|
597
|
+
return approvalOutput.parse(await stageEscape({
|
|
598
|
+
request,
|
|
599
|
+
cwd,
|
|
600
|
+
mode,
|
|
601
|
+
denialLine: lastDenialLine,
|
|
602
|
+
context,
|
|
603
|
+
executionPath: path,
|
|
604
|
+
}));
|
|
605
|
+
}
|
|
606
|
+
if (request.operation === "run") {
|
|
607
|
+
const result = await execute(root, path, home, request, context, sandbox, mode);
|
|
608
|
+
lastDenialLine = result.sandbox.likelyDenied
|
|
609
|
+
? (result.stderr
|
|
610
|
+
.split("\n")
|
|
611
|
+
.find((line) => classifySandboxDiagnostic(line) === "denial") ??
|
|
612
|
+
null)
|
|
613
|
+
: null;
|
|
614
|
+
return result;
|
|
615
|
+
}
|
|
616
|
+
if (processes !== undefined) {
|
|
617
|
+
const output = await callBackgroundExec(processes, request, (requested) => resolveCwd(root, requested), path, context.signal, sandbox);
|
|
618
|
+
return jsonValueSchema.parse("text" in output
|
|
619
|
+
? {
|
|
620
|
+
...output,
|
|
621
|
+
sandbox: {
|
|
622
|
+
mode,
|
|
623
|
+
likelyDenied: likelySandboxDenied(mode, {
|
|
624
|
+
status: "failed",
|
|
625
|
+
exitCode: output.process.exitCode ?? 0,
|
|
626
|
+
stderr: output.text,
|
|
627
|
+
stdout: "",
|
|
628
|
+
truncated: false,
|
|
629
|
+
}),
|
|
630
|
+
ranOutsideSandbox: sandbox === undefined,
|
|
631
|
+
},
|
|
632
|
+
}
|
|
633
|
+
: output);
|
|
634
|
+
}
|
|
495
635
|
throw new Error(`Computer capability is unavailable: ${selected}`);
|
|
496
636
|
},
|
|
497
637
|
});
|
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
import { type FilesPort } from "@fungi.computer/knapsack";
|
|
2
|
+
import type { LocalPermissionMode } from "./private-local-permission-mode.js";
|
|
2
3
|
import type { LocalSandboxLaunch } from "./private-invocation-computer.js";
|
|
3
4
|
/** Same live authority as exec; the policy module owns protected-path checks. */
|
|
4
5
|
export type LocalSandboxWrites = Readonly<{
|
|
5
|
-
assertWrite(
|
|
6
|
+
assertWrite(paths: readonly string[]): void;
|
|
6
7
|
launch: LocalSandboxLaunch;
|
|
7
8
|
}>;
|
|
8
9
|
/**
|
|
@@ -27,4 +28,4 @@ export declare class FilesPathError extends Error {
|
|
|
27
28
|
* in-root symlinks. This is trusted same-user containment, not protection from
|
|
28
29
|
* a same-user process racing pathname resolution.
|
|
29
30
|
*/
|
|
30
|
-
export declare function createPrivateInvocationFilesPort(path: string, scanBounds?: ScanBounds, sandbox?: LocalSandboxWrites): Promise<FilesPort>;
|
|
31
|
+
export declare function createPrivateInvocationFilesPort(path: string, scanBounds?: ScanBounds, sandbox?: LocalSandboxWrites, mode?: () => LocalPermissionMode): Promise<FilesPort>;
|
|
@@ -7,6 +7,15 @@ import { RE2JS } from "re2js";
|
|
|
7
7
|
import { createPrivateFilesPatch } from "./private-files-patch.js";
|
|
8
8
|
import { fitLocalImage, maximumLocalImageInputBytes, } from "./local-image-fit.js";
|
|
9
9
|
import writeFileAtomic from "write-file-atomic";
|
|
10
|
+
const readOnlyFilesMessage = "Read-only mode: switch to Workspace to edit";
|
|
11
|
+
/** An expected Files refusal, not a proposal for an approval. */
|
|
12
|
+
class ReadOnlyFilesRefusal extends FilesEditError {
|
|
13
|
+
constructor(path) {
|
|
14
|
+
super("read-only", path, readOnlyFilesMessage);
|
|
15
|
+
this.name = "ReadOnlyFilesRefusal";
|
|
16
|
+
this.message = readOnlyFilesMessage;
|
|
17
|
+
}
|
|
18
|
+
}
|
|
10
19
|
const maximumFileBytes = 128 * 1_024;
|
|
11
20
|
const defaultScanBounds = {
|
|
12
21
|
candidates: 50_000,
|
|
@@ -472,10 +481,14 @@ function createWriteQueue() {
|
|
|
472
481
|
* in-root symlinks. This is trusted same-user containment, not protection from
|
|
473
482
|
* a same-user process racing pathname resolution.
|
|
474
483
|
*/
|
|
475
|
-
export async function createPrivateInvocationFilesPort(path, scanBounds = defaultScanBounds, sandbox) {
|
|
484
|
+
export async function createPrivateInvocationFilesPort(path, scanBounds = defaultScanBounds, sandbox, mode = () => "full-access") {
|
|
476
485
|
const root = await InvocationRoot.capture(path);
|
|
477
486
|
const write = createWriteQueue();
|
|
478
487
|
const port = {
|
|
488
|
+
assertEditable(requested) {
|
|
489
|
+
if (mode() === "read-only")
|
|
490
|
+
throw new ReadOnlyFilesRefusal(requested);
|
|
491
|
+
},
|
|
479
492
|
async read(input, context) {
|
|
480
493
|
return await readPage(await root.existing(input.path, context), input.offset, input.limit, context);
|
|
481
494
|
},
|
|
@@ -491,6 +504,7 @@ export async function createPrivateInvocationFilesPort(path, scanBounds = defaul
|
|
|
491
504
|
async replace(input, context) {
|
|
492
505
|
return await write(async () => {
|
|
493
506
|
checkCancellation(context);
|
|
507
|
+
port.assertEditable?.(input.path);
|
|
494
508
|
const path = await root.existing(input.path, context);
|
|
495
509
|
const current = await readBoundedText(path, context);
|
|
496
510
|
if (current !== input.expectedContent)
|
|
@@ -500,7 +514,7 @@ export async function createPrivateInvocationFilesPort(path, scanBounds = defaul
|
|
|
500
514
|
throw new FilesEditError("file-too-large", input.path, `Result exceeds ${String(maximumFileBytes)} bytes; use files.patch for a small change`);
|
|
501
515
|
}
|
|
502
516
|
checkCancellation(context);
|
|
503
|
-
sandbox?.assertWrite(path.resolved);
|
|
517
|
+
sandbox?.assertWrite([path.resolved]);
|
|
504
518
|
await writeFileAtomic(path.resolved, input.content, {
|
|
505
519
|
encoding: "utf8",
|
|
506
520
|
});
|
|
@@ -515,6 +529,11 @@ export async function createPrivateInvocationFilesPort(path, scanBounds = defaul
|
|
|
515
529
|
return Object.freeze(port);
|
|
516
530
|
return Object.freeze({
|
|
517
531
|
...port,
|
|
518
|
-
patch: (input, context) => write(() =>
|
|
532
|
+
patch: (input, context) => write(async () => {
|
|
533
|
+
checkCancellation(context);
|
|
534
|
+
if (mode() === "read-only")
|
|
535
|
+
throw new ReadOnlyFilesRefusal(root.canonicalPath);
|
|
536
|
+
return patch(input, context);
|
|
537
|
+
}),
|
|
519
538
|
});
|
|
520
539
|
}
|
|
@@ -70,7 +70,7 @@ export declare function createPrivateLocalAgentControl(input: Readonly<{
|
|
|
70
70
|
list: (signal: AbortSignal, options?: Readonly<{
|
|
71
71
|
archived?: boolean;
|
|
72
72
|
}>) => Promise<readonly AgentControlAgent[]>;
|
|
73
|
-
send?: (input: import("@fungi.computer/shiitake/agent-control").AgentDelegationInput, sourceSessionId: string, signal: AbortSignal, invocation?: import("@fungi.computer/shiitake/agent-control").AgentDelegationInvocation) => Promise<import("@fungi.computer/shiitake").Accepted>;
|
|
73
|
+
send?: (input: import("@fungi.computer/shiitake/agent-control").AgentDelegationInput, sourceSessionId: string, signal: AbortSignal, invocation?: import("@fungi.computer/shiitake/agent-control").AgentDelegationInvocation) => Promise<import("@fungi.computer/shiitake").Accepted | import("@fungi.computer/shiitake").Held>;
|
|
74
74
|
recall?: (input: import("@fungi.computer/shiitake/agent-control").AgentRecallInput, sourceSessionId: string, signal: AbortSignal) => Promise<import("@fungi.computer/shiitake").CancelResult>;
|
|
75
75
|
create?: (input: Readonly<{
|
|
76
76
|
requestId: string;
|
|
@@ -91,7 +91,7 @@ export declare function createPrivateLocalAgentControl(input: Readonly<{
|
|
|
91
91
|
models?: (signal: AbortSignal) => Promise<readonly import("@fungi.computer/shiitake/agent-control").AgentControlModel[]>;
|
|
92
92
|
checkModel?: (input: Readonly<{
|
|
93
93
|
agentId: string;
|
|
94
|
-
sessionId
|
|
94
|
+
sessionId?: string;
|
|
95
95
|
}>, signal: AbortSignal) => Promise<void>;
|
|
96
96
|
open: (agentId: string, signal: AbortSignal) => Promise<Readonly<{
|
|
97
97
|
agent: import("@fungi.computer/shiitake").ShiitakeAgent;
|