toolcraft 0.0.863 → 0.0.865
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/composition.json +2 -2
- package/dist/composition.json +2 -2
- package/node_modules/@poe-code/safe-fs/README.md +2 -0
- package/node_modules/@poe-code/safe-fs/dist/python/directory.d.ts +9 -0
- package/node_modules/@poe-code/safe-fs/dist/python/directory.js +38 -0
- package/node_modules/@poe-code/safe-fs/dist/python/filesystem.d.ts +2 -2
- package/node_modules/@poe-code/safe-fs/dist/python/filesystem.js +45 -30
- package/node_modules/@poe-code/safe-fs/dist/python/request.js +1 -0
- package/node_modules/tiny-stdio-mcp-server/dist/composition.json +1 -1
- package/node_modules/toolcraft-schema/package.json +1 -1
- package/package.json +2 -2
package/composition.json
CHANGED
|
@@ -138,7 +138,7 @@
|
|
|
138
138
|
},
|
|
139
139
|
{
|
|
140
140
|
"name": "toolcraft",
|
|
141
|
-
"version": "0.0.
|
|
141
|
+
"version": "0.0.865",
|
|
142
142
|
"license": "MIT"
|
|
143
143
|
},
|
|
144
144
|
{
|
|
@@ -148,7 +148,7 @@
|
|
|
148
148
|
},
|
|
149
149
|
{
|
|
150
150
|
"name": "toolcraft-schema",
|
|
151
|
-
"version": "0.0.
|
|
151
|
+
"version": "0.0.865",
|
|
152
152
|
"license": "MIT"
|
|
153
153
|
},
|
|
154
154
|
{
|
package/dist/composition.json
CHANGED
|
@@ -138,7 +138,7 @@
|
|
|
138
138
|
},
|
|
139
139
|
{
|
|
140
140
|
"name": "toolcraft",
|
|
141
|
-
"version": "0.0.
|
|
141
|
+
"version": "0.0.865",
|
|
142
142
|
"license": "MIT"
|
|
143
143
|
},
|
|
144
144
|
{
|
|
@@ -148,7 +148,7 @@
|
|
|
148
148
|
},
|
|
149
149
|
{
|
|
150
150
|
"name": "toolcraft-schema",
|
|
151
|
-
"version": "0.0.
|
|
151
|
+
"version": "0.0.865",
|
|
152
152
|
"license": "MIT"
|
|
153
153
|
},
|
|
154
154
|
{
|
|
@@ -119,6 +119,8 @@ identities without a default device-count budget. Their optional `maxDevices` ac
|
|
|
119
119
|
`Infinity` or a positive safe integer. Explicit budgets remain enforced; device numbers
|
|
120
120
|
must still fit the disjoint application/runtime ranges of the uint32 Python stat ABI.
|
|
121
121
|
|
|
122
|
+
Python filesystem directory cursors use `iterateDirectory` when available, share open-handle admission with files, and drain pending reads before cleanup. Backends without lazy enumeration retain the buffered `readdir` fallback.
|
|
123
|
+
|
|
122
124
|
`withObjectFileDescriptors(fs, store)` supports large shell and Python descriptor
|
|
123
125
|
writes when the host supplies `store.createStaging`: private externally backed
|
|
124
126
|
pages keep working memory bounded without publishing the growing file after
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import type { FileSystem, FsOptions } from '../contracts/filesystem.js';
|
|
2
|
+
/** One prefetched entry makes acquisition errors eager without buffering a listing. */
|
|
3
|
+
export declare class PythonDirectory {
|
|
4
|
+
#private;
|
|
5
|
+
constructor(fs: FileSystem, path: string, options: FsOptions, limit: number);
|
|
6
|
+
prepare(): Promise<void>;
|
|
7
|
+
next(): Promise<string | null>;
|
|
8
|
+
close(): Promise<void>;
|
|
9
|
+
}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import { FsError } from '../contracts/errors.js';
|
|
2
|
+
/** One prefetched entry makes acquisition errors eager without buffering a listing. */
|
|
3
|
+
export class PythonDirectory {
|
|
4
|
+
#first;
|
|
5
|
+
#tail = Promise.resolve();
|
|
6
|
+
#closing;
|
|
7
|
+
#count = 0;
|
|
8
|
+
#iterator;
|
|
9
|
+
#options;
|
|
10
|
+
#limit;
|
|
11
|
+
constructor(fs, path, options, limit) {
|
|
12
|
+
this.#options = options;
|
|
13
|
+
this.#limit = limit;
|
|
14
|
+
const source = fs.iterateDirectory ? fs.iterateDirectory(path, options) : (async function* () { yield* await fs.readdir(path, { ...options, ...limit === Infinity ? {} : { maxEntries: limit } }); })();
|
|
15
|
+
this.#iterator = source[Symbol.asyncIterator]();
|
|
16
|
+
}
|
|
17
|
+
async prepare() { this.#first = await this.#iterator.next(); }
|
|
18
|
+
next() {
|
|
19
|
+
if (this.#closing)
|
|
20
|
+
return Promise.reject(new FsError('EBADF'));
|
|
21
|
+
const work = this.#tail.then(async () => {
|
|
22
|
+
this.#options.signal?.throwIfAborted();
|
|
23
|
+
const value = this.#first ?? await this.#iterator.next();
|
|
24
|
+
this.#first = undefined;
|
|
25
|
+
this.#options.signal?.throwIfAborted();
|
|
26
|
+
if (value.done)
|
|
27
|
+
return null;
|
|
28
|
+
if (++this.#count > this.#limit)
|
|
29
|
+
throw new FsError('EFBIG', { syscall: 'readdir' });
|
|
30
|
+
return value.value.name;
|
|
31
|
+
});
|
|
32
|
+
this.#tail = work.catch(() => { });
|
|
33
|
+
return work;
|
|
34
|
+
}
|
|
35
|
+
close() {
|
|
36
|
+
return this.#closing ??= this.#tail.then(async () => { this.#first = undefined; await this.#iterator.return?.(); });
|
|
37
|
+
}
|
|
38
|
+
}
|
|
@@ -9,7 +9,7 @@ export type PythonFsRequest = {
|
|
|
9
9
|
readonly op: "write";
|
|
10
10
|
readonly args: readonly [number, Uint8Array, number | null];
|
|
11
11
|
} | {
|
|
12
|
-
readonly op: "fstat" | "close" | "position" | "descriptorCapabilities";
|
|
12
|
+
readonly op: "directoryNext" | "fstat" | "close" | "position" | "descriptorCapabilities";
|
|
13
13
|
readonly args: readonly [number];
|
|
14
14
|
} | {
|
|
15
15
|
readonly op: "ftruncate";
|
|
@@ -18,7 +18,7 @@ export type PythonFsRequest = {
|
|
|
18
18
|
readonly op: "sync";
|
|
19
19
|
readonly args: readonly [number, boolean];
|
|
20
20
|
} | {
|
|
21
|
-
readonly op: "stat" | "lstat" | "readdir" | "realpath" | "readlink" | "rm" | "rmdir" | "rmtree" | "rmtreeSupported";
|
|
21
|
+
readonly op: "directoryOpen" | "stat" | "lstat" | "readdir" | "realpath" | "readlink" | "rm" | "rmdir" | "rmtree" | "rmtreeSupported";
|
|
22
22
|
readonly args: readonly [string];
|
|
23
23
|
} | {
|
|
24
24
|
readonly op: "rename" | "symlink" | "link";
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { PythonDirectory } from "./directory.js";
|
|
1
2
|
import { parsePythonFsRequest } from "./request.js";
|
|
2
3
|
import { composeAbortSignals } from "../contracts/abort.js";
|
|
3
4
|
import { FsError } from "../contracts/errors.js";
|
|
@@ -57,10 +58,37 @@ export class PythonFileSystem {
|
|
|
57
58
|
}
|
|
58
59
|
#handle(id) {
|
|
59
60
|
const handle = this.#handles.get(id);
|
|
60
|
-
if (!handle)
|
|
61
|
+
if (!handle || handle instanceof PythonDirectory)
|
|
61
62
|
throw new FsError("EBADF");
|
|
62
63
|
return handle;
|
|
63
64
|
}
|
|
65
|
+
async #acquire(open) {
|
|
66
|
+
if (this.#handles.size + this.#acquiring >= this.#limit || !Number.isSafeInteger(this.#next))
|
|
67
|
+
throw new FsError("EMFILE", { syscall: "open" });
|
|
68
|
+
this.#acquiring++;
|
|
69
|
+
let handle;
|
|
70
|
+
try {
|
|
71
|
+
handle = await open();
|
|
72
|
+
if (handle instanceof PythonDirectory)
|
|
73
|
+
await handle.prepare();
|
|
74
|
+
this.#scope.signal.throwIfAborted();
|
|
75
|
+
const id = this.#next++;
|
|
76
|
+
this.#handles.set(id, handle);
|
|
77
|
+
return id;
|
|
78
|
+
}
|
|
79
|
+
catch (error) {
|
|
80
|
+
if (handle)
|
|
81
|
+
try {
|
|
82
|
+
await handle.close();
|
|
83
|
+
}
|
|
84
|
+
catch { /* Preserve acquisition failure. */ }
|
|
85
|
+
this.#scope.signal.throwIfAborted();
|
|
86
|
+
throw error;
|
|
87
|
+
}
|
|
88
|
+
finally {
|
|
89
|
+
this.#acquiring--;
|
|
90
|
+
}
|
|
91
|
+
}
|
|
64
92
|
#size(size) {
|
|
65
93
|
if (!Number.isSafeInteger(size) || size < 0)
|
|
66
94
|
throw new FsError("EINVAL");
|
|
@@ -70,7 +98,9 @@ export class PythonFileSystem {
|
|
|
70
98
|
async #execute(request) {
|
|
71
99
|
if (request.op === "close") {
|
|
72
100
|
const [id] = request.args;
|
|
73
|
-
const handle = this.#
|
|
101
|
+
const handle = this.#handles.get(id);
|
|
102
|
+
if (!handle)
|
|
103
|
+
throw new FsError("EBADF");
|
|
74
104
|
this.#handles.delete(id);
|
|
75
105
|
await handle.close({ signal: this.#scope.signal });
|
|
76
106
|
return;
|
|
@@ -82,40 +112,25 @@ export class PythonFileSystem {
|
|
|
82
112
|
switch (request.op) {
|
|
83
113
|
case "open": {
|
|
84
114
|
const [path, supplied] = request.args;
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
this.#acquiring++;
|
|
90
|
-
let handle;
|
|
91
|
-
try {
|
|
92
|
-
// A following capability query cannot preflight exclusive final-entry acquisition.
|
|
93
|
-
// The canonical open remains authoritative for selected-path policy and atomic refusal.
|
|
115
|
+
return this.#acquire(async () => {
|
|
116
|
+
if (!this.#open && !fs.open)
|
|
117
|
+
throw new FsError("ENOTSUP", { syscall: "open", path });
|
|
118
|
+
// Exclusive creation must reach the canonical atomic operation directly.
|
|
94
119
|
const capabilities = supplied.creation !== "exclusive" && fs.capabilitiesFor ? await fs.capabilitiesFor(this.#path(path), { signal, ...(supplied.creation === "ifMissing" ? { create: true } : {}) }) : fs.capabilities;
|
|
95
120
|
if (capabilities.readOnly === true && (supplied.access === "write" || supplied.access === "readwrite" || supplied.creation === "ifMissing" || supplied.creation === "exclusive" || supplied.truncate === true || supplied.append === true))
|
|
96
121
|
throw new FsError("EROFS", { syscall: "open", path });
|
|
97
122
|
if (capabilities.open === false)
|
|
98
123
|
throw new FsError("ENOTSUP", { syscall: "open", path });
|
|
99
124
|
signal.throwIfAborted();
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
try {
|
|
110
|
-
await handle.close();
|
|
111
|
-
}
|
|
112
|
-
catch { /* Preserve the acquisition failure. */ }
|
|
113
|
-
signal.throwIfAborted();
|
|
114
|
-
throw error;
|
|
115
|
-
}
|
|
116
|
-
finally {
|
|
117
|
-
this.#acquiring--;
|
|
118
|
-
}
|
|
125
|
+
return (this.#open ?? fs.open.bind(fs))(this.#path(path), { ...supplied, signal });
|
|
126
|
+
});
|
|
127
|
+
}
|
|
128
|
+
case "directoryOpen": return this.#acquire(() => new PythonDirectory(fs, this.#path(request.args[0]), options, this.#directoryLimit));
|
|
129
|
+
case "directoryNext": {
|
|
130
|
+
const handle = this.#handles.get(request.args[0]);
|
|
131
|
+
if (!(handle instanceof PythonDirectory))
|
|
132
|
+
throw new FsError("EBADF");
|
|
133
|
+
return handle.next();
|
|
119
134
|
}
|
|
120
135
|
case "descriptorCapabilities": return { ...this.#handle(request.args[0]).capabilities };
|
|
121
136
|
case "fstat": return this.#handle(request.args[0]).stat(options);
|
|
@@ -2,6 +2,7 @@ import { FsError } from "../contracts/errors.js";
|
|
|
2
2
|
const argumentKinds = {
|
|
3
3
|
open: ["string", "object"], read: ["number", "number", "position"], write: ["number", "bytes", "position"],
|
|
4
4
|
descriptorCapabilities: ["number"], fstat: ["number"], close: ["number"], position: ["number"], ftruncate: ["number", "number"], sync: ["number", "boolean"],
|
|
5
|
+
directoryOpen: ["string"], directoryNext: ["number"],
|
|
5
6
|
stat: ["string"], lstat: ["string"], readdir: ["string"], realpath: ["string"], readlink: ["string"], rm: ["string"], rmdir: ["string"], rmtree: ["string"], rmtreeSupported: ["string"],
|
|
6
7
|
rename: ["string", "string"], symlink: ["string", "string"], link: ["string", "string"], mkdir: ["string", "optionalObject"],
|
|
7
8
|
chmod: ["string", "number"], truncate: ["string", "number"], access: ["string", "number"], utimes: ["string", "number", "number"],
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "toolcraft",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.865",
|
|
4
4
|
"license": "MIT",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/index.js",
|
|
@@ -177,7 +177,7 @@
|
|
|
177
177
|
"saxes"
|
|
178
178
|
],
|
|
179
179
|
"optionalDependencies": {
|
|
180
|
-
"toolcraft-schema": "0.0.
|
|
180
|
+
"toolcraft-schema": "0.0.865",
|
|
181
181
|
"toolcraft-design": "*",
|
|
182
182
|
"@poe-code/frontmatter": "*",
|
|
183
183
|
"@poe-code/agent-mcp-config": "*",
|