@fungi.computer/knapsack 0.2.0 → 0.3.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/README.md +5 -1
- package/dist/files.d.ts +5 -1
- package/dist/files.js +15 -6
- package/dist/index.d.ts +3 -0
- package/dist/index.js +1 -0
- package/dist/mcp.d.ts +1 -2
- package/docs/api/index.md +2 -1
- package/package.json +3 -3
package/README.md
CHANGED
|
@@ -98,7 +98,11 @@ invalidates its captured modules when that list changes.
|
|
|
98
98
|
Computer calls preserve Mycelium's invocation context, including its execution
|
|
99
99
|
identity, abort signal and deadline. Advertised Computer operations are marked
|
|
100
100
|
unsafe to replay because their implementation can change external state. The
|
|
101
|
-
host's provider remains responsible for admission on each call.
|
|
101
|
+
host's provider remains responsible for admission on each call. An interactive
|
|
102
|
+
host may set `confirmCalls: true` on its Computer connection to request Mycelium
|
|
103
|
+
confirmation before every advertised call. The advertisement does not
|
|
104
|
+
distinguish read-only from mutating operations, so this policy confirms both;
|
|
105
|
+
other connections keep their existing approval policy.
|
|
102
106
|
|
|
103
107
|
`Knapsack.files({ port })` builds a Files module and direct tools from one port.
|
|
104
108
|
Text edits use `port.replace` with the expected original content; the host must
|
package/dist/files.d.ts
CHANGED
|
@@ -5,7 +5,7 @@ import { z } from "zod";
|
|
|
5
5
|
import { readInputSchema, readGuestOutputSchema, readOutputSchema } from "./files-read.js";
|
|
6
6
|
export { readGuestOutputSchema, readInputSchema, readOutputSchema };
|
|
7
7
|
/** Typed refusal from a bounded or conditional Files edit. */
|
|
8
|
-
export type FilesEditErrorReason = "not-found" | "ambiguous" | "file-too-large" | "partial-character" | "unchanged" | "not-text" | "conflict";
|
|
8
|
+
export type FilesEditErrorReason = "not-found" | "ambiguous" | "file-too-large" | "partial-character" | "unchanged" | "not-text" | "conflict" | "read-only";
|
|
9
9
|
/** Expected Files edit refusal, kept distinct from unexpected host failures. */
|
|
10
10
|
export declare class FilesEditError extends Error {
|
|
11
11
|
readonly reason: FilesEditErrorReason;
|
|
@@ -68,6 +68,7 @@ export declare const filesEditFailureSchema: z.ZodLazy<z.ZodObject<{
|
|
|
68
68
|
unchanged: "unchanged";
|
|
69
69
|
"not-text": "not-text";
|
|
70
70
|
conflict: "conflict";
|
|
71
|
+
"read-only": "read-only";
|
|
71
72
|
}>;
|
|
72
73
|
path: z.ZodLazy<z.ZodString>;
|
|
73
74
|
message: z.ZodString;
|
|
@@ -95,6 +96,7 @@ export declare const editOutputSchema: z.ZodUnion<readonly [z.ZodLazy<z.ZodObjec
|
|
|
95
96
|
unchanged: "unchanged";
|
|
96
97
|
"not-text": "not-text";
|
|
97
98
|
conflict: "conflict";
|
|
99
|
+
"read-only": "read-only";
|
|
98
100
|
}>;
|
|
99
101
|
path: z.ZodLazy<z.ZodString>;
|
|
100
102
|
message: z.ZodString;
|
|
@@ -104,6 +106,8 @@ export declare const editOutputSchema: z.ZodUnion<readonly [z.ZodLazy<z.ZodObjec
|
|
|
104
106
|
export type FilesPatch = (input: z.output<typeof patchInputSchema>, context: InvocationContext) => Promise<z.output<typeof patchOutputSchema>>;
|
|
105
107
|
/** Captured host file authority; the host owns path confinement and atomic replacement. */
|
|
106
108
|
export type FilesPort = Readonly<{
|
|
109
|
+
/** Host-selected mode gate, before an edit reads or computes a replacement. */
|
|
110
|
+
assertEditable?: (path: string) => void;
|
|
107
111
|
/** Omitted when this Files host has no qualified native patch engine. */
|
|
108
112
|
patch?: FilesPatch;
|
|
109
113
|
/** Read a bounded text page. */
|
package/dist/files.js
CHANGED
|
@@ -97,6 +97,7 @@ export const filesEditFailureSchema = z.lazy(() => z.strictObject({
|
|
|
97
97
|
"not-text",
|
|
98
98
|
"unchanged",
|
|
99
99
|
"conflict",
|
|
100
|
+
"read-only",
|
|
100
101
|
]),
|
|
101
102
|
path: pathSchema,
|
|
102
103
|
message: z.string(),
|
|
@@ -255,6 +256,7 @@ function generateDiffString(oldContent, newContent, context = 4) {
|
|
|
255
256
|
return { diff: output.join("\n"), firstChangedLine };
|
|
256
257
|
}
|
|
257
258
|
async function applyEdit(port, input, context) {
|
|
259
|
+
port.assertEditable?.(input.path);
|
|
258
260
|
const current = readOutputSchema.parse(await port.read({
|
|
259
261
|
path: input.path,
|
|
260
262
|
offset: 0,
|
|
@@ -321,9 +323,16 @@ export async function edit(port, input, context) {
|
|
|
321
323
|
}
|
|
322
324
|
}
|
|
323
325
|
/** Cancellation does not finish until the Files owner has acknowledged cleanup. */
|
|
324
|
-
function runFileOperation(invoke) {
|
|
326
|
+
function runFileOperation(invoke, toolCallId) {
|
|
325
327
|
return Effect.callback((resume, signal) => {
|
|
326
|
-
const
|
|
328
|
+
const context = {
|
|
329
|
+
executionId: toolCallId ?? nanoid(),
|
|
330
|
+
deadlineAt: null,
|
|
331
|
+
signal,
|
|
332
|
+
};
|
|
333
|
+
if (toolCallId !== undefined)
|
|
334
|
+
Object.assign(context, { toolCallId });
|
|
335
|
+
const pending = invoke(context);
|
|
327
336
|
void pending.then((result) => {
|
|
328
337
|
resume(Effect.succeed(result));
|
|
329
338
|
}, (cause) => {
|
|
@@ -336,8 +345,8 @@ function runFileOperation(invoke) {
|
|
|
336
345
|
return Effect.promise(() => pending.then(() => undefined, () => undefined));
|
|
337
346
|
});
|
|
338
347
|
}
|
|
339
|
-
function runPatch(invoke, input) {
|
|
340
|
-
return runFileOperation((context) => invoke(input, context));
|
|
348
|
+
function runPatch(invoke, input, toolCallId) {
|
|
349
|
+
return runFileOperation((context) => invoke(input, context), toolCallId);
|
|
341
350
|
}
|
|
342
351
|
/** Keep model parameters while omitting the decoder's schema-document marker. */
|
|
343
352
|
function toolParameters(schema) {
|
|
@@ -369,7 +378,7 @@ function patchTool(invokePatch) {
|
|
|
369
378
|
message: cause instanceof Error ? cause.message : "Invalid patch input",
|
|
370
379
|
}),
|
|
371
380
|
}).pipe(Effect.map((value) => ({
|
|
372
|
-
execute: () => Stream.fromEffect(runPatch(invokePatch, value).pipe(Effect.map((result) => ({
|
|
381
|
+
execute: () => Stream.fromEffect(runPatch(invokePatch, value, call.id).pipe(Effect.map((result) => ({
|
|
373
382
|
type: "result",
|
|
374
383
|
result: {
|
|
375
384
|
content: [
|
|
@@ -378,7 +387,7 @@ function patchTool(invokePatch) {
|
|
|
378
387
|
text: JSON.stringify(result),
|
|
379
388
|
},
|
|
380
389
|
],
|
|
381
|
-
isError: result.status !== "completed",
|
|
390
|
+
isError: "status" in result && result.status !== "completed",
|
|
382
391
|
},
|
|
383
392
|
})))),
|
|
384
393
|
}))),
|
package/dist/index.d.ts
CHANGED
|
@@ -15,6 +15,9 @@ export type ComputerConnectionOptions = Readonly<{
|
|
|
15
15
|
name: string;
|
|
16
16
|
/** Logical parent namespace; the Computer name is its final segment. */
|
|
17
17
|
namespace?: readonly string[];
|
|
18
|
+
/** Ask a person before each advertised call on this connection. The host
|
|
19
|
+
* chooses this for interactive MCP; Agent connections keep their own policy. */
|
|
20
|
+
confirmCalls?: boolean;
|
|
18
21
|
/** Files scoped by the host to this Computer, under its own authority id. */
|
|
19
22
|
files?: FilesConnectionOptions;
|
|
20
23
|
/**
|
package/dist/index.js
CHANGED
|
@@ -57,6 +57,7 @@ function projectComputer(input, advertisement) {
|
|
|
57
57
|
operations[advertised.id] = Mycelium.operation({
|
|
58
58
|
// Computer advertisements may execute arbitrary host actions.
|
|
59
59
|
replay: "unsafe",
|
|
60
|
+
confirm: input.confirmCalls === true,
|
|
60
61
|
description: advertised.description,
|
|
61
62
|
input: operationSchema(advertised.parameters),
|
|
62
63
|
output,
|
package/dist/mcp.d.ts
CHANGED
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
import type { FetchLike } from "@modelcontextprotocol/client";
|
|
2
1
|
import { type CapabilitySource } from "@fungi.computer/mycelium";
|
|
3
2
|
/** One remote MCP server a host has admitted for an Agent. */
|
|
4
3
|
export type McpServerConnection = Readonly<{
|
|
@@ -28,7 +27,7 @@ export type McpServersConnectionOptions = Readonly<{
|
|
|
28
27
|
/** Receives each server dropped from an acquisition and why. */
|
|
29
28
|
onUnavailable: (error: McpServerUnavailableError) => void;
|
|
30
29
|
/** Host egress for every MCP request; the host chooses where requests may go. */
|
|
31
|
-
fetch:
|
|
30
|
+
fetch: (url: string | URL, init?: RequestInit) => Promise<Response>;
|
|
32
31
|
/** Parent namespace for every server module; defaults to `["mcp"]`. */
|
|
33
32
|
namespace?: readonly string[];
|
|
34
33
|
/** Bound on connecting and listing one server; defaults to 10 seconds. */
|
package/docs/api/index.md
CHANGED
|
@@ -343,6 +343,7 @@ type ComputerConnectionOptions = Readonly<{
|
|
|
343
343
|
id: string;
|
|
344
344
|
name: string;
|
|
345
345
|
namespace?: readonly string[];
|
|
346
|
+
confirmCalls?: boolean;
|
|
346
347
|
files?: FilesConnectionOptions;
|
|
347
348
|
role?: string;
|
|
348
349
|
computer: KnapsackComputer;
|
|
@@ -411,7 +412,7 @@ type McpServersConnectionOptions = Readonly<{
|
|
|
411
412
|
>;
|
|
412
413
|
subscribe: (invalidate) => () => void;
|
|
413
414
|
onUnavailable: (error) => void;
|
|
414
|
-
fetch:
|
|
415
|
+
fetch: (url, init?) => Promise<Response>;
|
|
415
416
|
namespace?: readonly string[];
|
|
416
417
|
connectTimeoutMillis?: number;
|
|
417
418
|
}>;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@fungi.computer/knapsack",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.3.0",
|
|
4
4
|
"author": "Levi <levi@technomancy.dev>",
|
|
5
5
|
"description": "Agent tool connections projected into Mycelium capability sources",
|
|
6
6
|
"type": "module",
|
|
@@ -47,8 +47,8 @@
|
|
|
47
47
|
"dependencies": {
|
|
48
48
|
"@fungi.computer/cairn": "0.1.0",
|
|
49
49
|
"@fungi.computer/forage": "0.2.0",
|
|
50
|
-
"@fungi.computer/mule": "0.
|
|
51
|
-
"@fungi.computer/mycelium": "0.2.
|
|
50
|
+
"@fungi.computer/mule": "0.2.0",
|
|
51
|
+
"@fungi.computer/mycelium": "0.2.2",
|
|
52
52
|
"@fungi.computer/whistle": "0.1.0",
|
|
53
53
|
"@modelcontextprotocol/client": "2.0.0",
|
|
54
54
|
"diff": "8.0.4",
|