poe-code 14.0.19 → 14.0.21
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/index.js +23 -12
- package/dist/index.js.map +2 -2
- package/dist/metafile.json +1 -1
- package/package.json +3 -10
- package/packages/safe-js/dist/chunks/{chunk-W6AHPZCN.js → chunk-5XRQ3GNN.js} +2 -2
- package/packages/safe-js/dist/chunks/{chunk-BW4SYRDC.js → chunk-CZNLD2Z3.js} +21 -3
- package/packages/safe-js/dist/chunks/{chunk-BW4SYRDC.js.map → chunk-CZNLD2Z3.js.map} +2 -2
- package/packages/safe-js/dist/cli.js +2 -2
- package/packages/safe-js/dist/core.js +1 -1
- package/packages/safe-js/dist/index.js +2 -2
- package/packages/safe-js/dist/realm.d.ts +3 -0
- package/packages/safe-js/dist/realm.js +18 -1
- package/packages/safe-js/dist/run.d.ts +1 -0
- package/packages/safe-js/dist/run.js +1 -1
- package/packages/safe-bash-mcp/dist/cli.d.ts +0 -2
- package/packages/safe-bash-mcp/dist/cli.js +0 -23
- package/packages/safe-bash-mcp/dist/index.d.ts +0 -21
- package/packages/safe-bash-mcp/dist/index.js +0 -141
- /package/packages/safe-js/dist/chunks/{chunk-W6AHPZCN.js.map → chunk-5XRQ3GNN.js.map} +0 -0
|
@@ -22,7 +22,7 @@ import {
|
|
|
22
22
|
resolveFsConfig,
|
|
23
23
|
splitFrontmatter,
|
|
24
24
|
withBrokenPipeGuard
|
|
25
|
-
} from "./chunks/chunk-
|
|
25
|
+
} from "./chunks/chunk-5XRQ3GNN.js";
|
|
26
26
|
import {
|
|
27
27
|
Budget,
|
|
28
28
|
SandboxError,
|
|
@@ -34,7 +34,7 @@ import {
|
|
|
34
34
|
replaceErrorStack,
|
|
35
35
|
restore,
|
|
36
36
|
run
|
|
37
|
-
} from "./chunks/chunk-
|
|
37
|
+
} from "./chunks/chunk-CZNLD2Z3.js";
|
|
38
38
|
import "./chunks/chunk-7M2SJPSZ.js";
|
|
39
39
|
import "./chunks/chunk-2QRYPKQQ.js";
|
|
40
40
|
import "./chunks/chunk-4YIDX44G.js";
|
|
@@ -26,7 +26,7 @@ import {
|
|
|
26
26
|
runWithSpawnUsageAccumulator,
|
|
27
27
|
splitFrontmatter,
|
|
28
28
|
supportsSpawnMode
|
|
29
|
-
} from "./chunks/chunk-
|
|
29
|
+
} from "./chunks/chunk-5XRQ3GNN.js";
|
|
30
30
|
import {
|
|
31
31
|
Budget,
|
|
32
32
|
FileSnapshotBackend,
|
|
@@ -50,7 +50,7 @@ import {
|
|
|
50
50
|
registerPendingHostCallPolicy,
|
|
51
51
|
restore,
|
|
52
52
|
run
|
|
53
|
-
} from "./chunks/chunk-
|
|
53
|
+
} from "./chunks/chunk-CZNLD2Z3.js";
|
|
54
54
|
import "./chunks/chunk-7M2SJPSZ.js";
|
|
55
55
|
import "./chunks/chunk-2QRYPKQQ.js";
|
|
56
56
|
import "./chunks/chunk-4YIDX44G.js";
|
|
@@ -18,6 +18,9 @@ export type RealmOptions = {
|
|
|
18
18
|
bindings?: Record<string, CallerInjectedBinding>;
|
|
19
19
|
modules?: ModuleRegistry;
|
|
20
20
|
extensions?: readonly SafeJSExtension[];
|
|
21
|
+
builtinOverrides?: {
|
|
22
|
+
console?: string;
|
|
23
|
+
};
|
|
21
24
|
grants?: readonly string[];
|
|
22
25
|
budget?: Budget;
|
|
23
26
|
signal?: AbortSignal;
|
|
@@ -32,6 +32,7 @@ class RealmState {
|
|
|
32
32
|
bridge;
|
|
33
33
|
limits;
|
|
34
34
|
extensions;
|
|
35
|
+
consoleExtension;
|
|
35
36
|
cleanups = [];
|
|
36
37
|
callbacks = new Map();
|
|
37
38
|
pendingCallbacks = new Set();
|
|
@@ -91,6 +92,14 @@ class RealmState {
|
|
|
91
92
|
const grants = new Set(readStringList(options.grants ?? [], "Realm grants"));
|
|
92
93
|
for (const extension of this.extensions)
|
|
93
94
|
getExtensionSetup(extension);
|
|
95
|
+
const overrides = readDataRecord(options.builtinOverrides === undefined ? {} : options.builtinOverrides, "Builtin overrides");
|
|
96
|
+
for (const [name, extensionName] of Object.entries(overrides)) {
|
|
97
|
+
if (name !== "console" || typeof extensionName !== "string" || extensionName.length === 0 || extensionName.length > 256)
|
|
98
|
+
throw new TypeError("Builtin overrides only supports console with a nonempty extension name.");
|
|
99
|
+
if (!this.extensions.some(extension => extension.manifest.name === extensionName && extension.manifest.globals?.includes(name)))
|
|
100
|
+
throw new TypeError("Console override requires a registered extension declaring console.");
|
|
101
|
+
this.consoleExtension = extensionName;
|
|
102
|
+
}
|
|
94
103
|
this.budget = options.budget ?? new Budget({ maxCallDepth: 1000 });
|
|
95
104
|
this.lease = this.budget.acquireCompileOwner(true);
|
|
96
105
|
this.compilation = new CompileScope(this.lease.owner);
|
|
@@ -111,7 +120,10 @@ class RealmState {
|
|
|
111
120
|
random: createReplayableRandom({ seed: options.randomSeed }).next
|
|
112
121
|
});
|
|
113
122
|
const names = new Set();
|
|
114
|
-
const globals = new Set([
|
|
123
|
+
const globals = new Set([
|
|
124
|
+
...Object.keys(this.builtinBindings).filter(name => name !== "console" || this.consoleExtension === undefined),
|
|
125
|
+
...Object.keys(this.globals)
|
|
126
|
+
]);
|
|
115
127
|
const modules = new Map(Object.entries(this.modules).map(([name, exports]) => [name, new Set(Object.keys(exports))]));
|
|
116
128
|
for (const extension of this.extensions) {
|
|
117
129
|
const manifest = extension.manifest;
|
|
@@ -123,6 +135,8 @@ class RealmState {
|
|
|
123
135
|
throw new TypeError(`Missing grant '${capability}' for extension '${manifest.name}'.`);
|
|
124
136
|
}
|
|
125
137
|
for (const name of manifest.globals ?? []) {
|
|
138
|
+
if (name === "console" && this.consoleExtension !== undefined && manifest.name !== this.consoleExtension)
|
|
139
|
+
throw new TypeError(`Conflicting global 'console': replacement is authorized only for '${this.consoleExtension}'.`);
|
|
126
140
|
if (globals.has(name))
|
|
127
141
|
throw new TypeError(`Conflicting global '${name}'.`);
|
|
128
142
|
globals.add(name);
|
|
@@ -539,6 +553,8 @@ class RealmState {
|
|
|
539
553
|
const globals = readDataRecord(exports.globals ?? {}, "Extension globals");
|
|
540
554
|
const modules = readModules(exports.modules);
|
|
541
555
|
assertNames(Object.keys(globals), extension.manifest.globals ?? [], "global");
|
|
556
|
+
if (extension.manifest.name === this.consoleExtension && !this.hostObjects.has(globals.console))
|
|
557
|
+
throw new TypeError("Console replacement must be a host object created in this realm.");
|
|
542
558
|
assertNames(Object.keys(modules), Object.keys(extension.manifest.modules ?? {}), "module");
|
|
543
559
|
for (const [name, values] of Object.entries(modules)) {
|
|
544
560
|
assertNames(Object.keys(values), extension.manifest.modules?.[name] ?? [], "module export");
|
|
@@ -794,6 +810,7 @@ function readRealmOptions(value, oneShot = false) {
|
|
|
794
810
|
"bindings",
|
|
795
811
|
"modules",
|
|
796
812
|
"extensions",
|
|
813
|
+
"builtinOverrides",
|
|
797
814
|
"grants",
|
|
798
815
|
"budget",
|
|
799
816
|
"signal",
|
|
@@ -14,6 +14,7 @@ import type { SnapshotBackend } from "./snapshot/backend.js";
|
|
|
14
14
|
import { type ReplayData } from "./snapshot/replay-data.js";
|
|
15
15
|
export type RunOptions = {
|
|
16
16
|
extensions?: RealmOptions["extensions"];
|
|
17
|
+
builtinOverrides?: RealmOptions["builtinOverrides"];
|
|
17
18
|
grants?: RealmOptions["grants"];
|
|
18
19
|
limits?: RealmOptions["limits"];
|
|
19
20
|
bindings?: Record<string, CallerInjectedBinding>;
|
|
@@ -39,7 +39,7 @@ export class UnhandledRejectionError extends Error {
|
|
|
39
39
|
}
|
|
40
40
|
const DEFAULT_MAX_CALL_DEPTH = 1_000;
|
|
41
41
|
export function run(source, options = {}) {
|
|
42
|
-
if (options.extensions !== undefined)
|
|
42
|
+
if (options.extensions !== undefined || options.builtinOverrides !== undefined)
|
|
43
43
|
return runWithExtensions(source, options);
|
|
44
44
|
const lifecycle = {
|
|
45
45
|
hostCallbackDepth: 0,
|
|
@@ -1,23 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
import { resolve } from "node:path";
|
|
3
|
-
import { pathToFileURL } from "node:url";
|
|
4
|
-
import { parseArgs } from "node:util";
|
|
5
|
-
import { createSafeBashMcpServer } from "./index.js";
|
|
6
|
-
const { values } = parseArgs({
|
|
7
|
-
options: { config: { type: "string" } },
|
|
8
|
-
strict: true,
|
|
9
|
-
allowPositionals: false
|
|
10
|
-
});
|
|
11
|
-
const options = values.config === undefined
|
|
12
|
-
? {}
|
|
13
|
-
: (await import(pathToFileURL(resolve(values.config)).href)).default;
|
|
14
|
-
if (options === undefined) {
|
|
15
|
-
throw new TypeError("The config module must default-export SafeBashMcpOptions");
|
|
16
|
-
}
|
|
17
|
-
const server = createSafeBashMcpServer(options);
|
|
18
|
-
try {
|
|
19
|
-
await server.listen();
|
|
20
|
-
}
|
|
21
|
-
finally {
|
|
22
|
-
await server.close();
|
|
23
|
-
}
|
|
@@ -1,21 +0,0 @@
|
|
|
1
|
-
import { Shell, type ShellOptions } from "poe-code/safe-bash";
|
|
2
|
-
import { type Server } from "../../tiny-stdio-mcp-server/dist/index.js";
|
|
3
|
-
export interface SafeBashMcpOptions extends Partial<ShellOptions> {
|
|
4
|
-
readonly createShell?: (options: ShellOptions) => Shell;
|
|
5
|
-
}
|
|
6
|
-
export interface ShellExecuteInput {
|
|
7
|
-
readonly command: string;
|
|
8
|
-
readonly cwd?: string;
|
|
9
|
-
readonly env?: Record<string, string>;
|
|
10
|
-
readonly stdin?: string;
|
|
11
|
-
}
|
|
12
|
-
export interface ShellExecuteOutput {
|
|
13
|
-
readonly stdout: string;
|
|
14
|
-
readonly stderr: string;
|
|
15
|
-
readonly exitCode: number;
|
|
16
|
-
}
|
|
17
|
-
export type SafeBashMcpServer = Server & {
|
|
18
|
-
readonly shell: Shell;
|
|
19
|
-
close(): Promise<void>;
|
|
20
|
-
};
|
|
21
|
-
export declare function createSafeBashMcpServer(options?: SafeBashMcpOptions): SafeBashMcpServer;
|
|
@@ -1,141 +0,0 @@
|
|
|
1
|
-
import { createMemoryFileSystem } from "poe-code/safe-fs";
|
|
2
|
-
import { CommandRegistry, Shell, createStandardCommands } from "poe-code/safe-bash";
|
|
3
|
-
import { createServer } from "tiny-stdio-mcp-server";
|
|
4
|
-
const inputSchema = {
|
|
5
|
-
type: "object",
|
|
6
|
-
properties: {
|
|
7
|
-
command: { type: "string", minLength: 1, description: "Bash source interpreted by safe-bash, never a host shell" },
|
|
8
|
-
cwd: { type: "string", description: "Working directory in the configured virtual filesystem for this call" },
|
|
9
|
-
env: { type: "object", additionalProperties: { type: "string" }, description: "Environment overrides for this call" },
|
|
10
|
-
stdin: { type: "string", description: "Standard input for this call" }
|
|
11
|
-
},
|
|
12
|
-
required: ["command"],
|
|
13
|
-
additionalProperties: false
|
|
14
|
-
};
|
|
15
|
-
const outputSchema = {
|
|
16
|
-
type: "object",
|
|
17
|
-
properties: {
|
|
18
|
-
stdout: { type: "string" },
|
|
19
|
-
stderr: { type: "string" },
|
|
20
|
-
exitCode: { type: "integer" }
|
|
21
|
-
},
|
|
22
|
-
required: ["stdout", "stderr", "exitCode"],
|
|
23
|
-
additionalProperties: false
|
|
24
|
-
};
|
|
25
|
-
export function createSafeBashMcpServer(options = {}) {
|
|
26
|
-
if (options === null || typeof options !== "object" || Array.isArray(options)) {
|
|
27
|
-
throw new TypeError("Safe-bash MCP options must be an object");
|
|
28
|
-
}
|
|
29
|
-
const { createShell, ...shellOptions } = options;
|
|
30
|
-
const resolvedOptions = {
|
|
31
|
-
...shellOptions,
|
|
32
|
-
fs: options.fs ?? createMemoryFileSystem(),
|
|
33
|
-
commands: options.commands ?? new CommandRegistry(createStandardCommands())
|
|
34
|
-
};
|
|
35
|
-
const shell = createShell ? createShell(resolvedOptions) : new Shell(resolvedOptions);
|
|
36
|
-
const lifetime = new AbortController();
|
|
37
|
-
let closing;
|
|
38
|
-
const close = () => {
|
|
39
|
-
if (closing === undefined) {
|
|
40
|
-
closing = shell.dispose();
|
|
41
|
-
lifetime.abort();
|
|
42
|
-
}
|
|
43
|
-
return closing;
|
|
44
|
-
};
|
|
45
|
-
let pending = Promise.resolve();
|
|
46
|
-
const server = createServer({ name: "safe-bash-mcp", version: "0.1.0" });
|
|
47
|
-
server.tool("shell_execute", "Execute Bash in a shared virtual filesystem. Calls run in order; cwd and environment changes do not persist across calls.", inputSchema, async ({ command, ...execOptions }) => {
|
|
48
|
-
const execution = pending.then(() => {
|
|
49
|
-
if (lifetime.signal.aborted)
|
|
50
|
-
throw new Error("Safe-bash MCP server is closed");
|
|
51
|
-
return shell.exec(command, execOptions);
|
|
52
|
-
});
|
|
53
|
-
pending = execution.then(() => undefined, () => undefined);
|
|
54
|
-
const { stdout, stderr, exitCode } = await execution;
|
|
55
|
-
const output = { stdout, stderr, exitCode };
|
|
56
|
-
return {
|
|
57
|
-
content: [{ type: "text", text: JSON.stringify(output) }],
|
|
58
|
-
structuredContent: output,
|
|
59
|
-
isError: exitCode !== 0
|
|
60
|
-
};
|
|
61
|
-
}, outputSchema);
|
|
62
|
-
const connect = server.connect;
|
|
63
|
-
return Object.assign(server, {
|
|
64
|
-
shell,
|
|
65
|
-
close,
|
|
66
|
-
async connect(transport) {
|
|
67
|
-
const readable = new PassThrough();
|
|
68
|
-
let stopped = false;
|
|
69
|
-
let failure;
|
|
70
|
-
const stop = () => {
|
|
71
|
-
if (stopped)
|
|
72
|
-
return;
|
|
73
|
-
stopped = true;
|
|
74
|
-
void close().catch(() => undefined);
|
|
75
|
-
transport.readable.unpipe(readable);
|
|
76
|
-
readable.end();
|
|
77
|
-
};
|
|
78
|
-
const fail = (error) => {
|
|
79
|
-
failure ??= error;
|
|
80
|
-
stop();
|
|
81
|
-
};
|
|
82
|
-
const writable = new Writable({
|
|
83
|
-
write(chunk, encoding, callback) {
|
|
84
|
-
if (!stopped) {
|
|
85
|
-
try {
|
|
86
|
-
transport.writable.write(chunk, encoding);
|
|
87
|
-
}
|
|
88
|
-
catch (error) {
|
|
89
|
-
fail(error);
|
|
90
|
-
}
|
|
91
|
-
}
|
|
92
|
-
callback();
|
|
93
|
-
}
|
|
94
|
-
});
|
|
95
|
-
const connection = connect({ readable, writable });
|
|
96
|
-
transport.readable.once("end", stop);
|
|
97
|
-
transport.readable.once("close", stop);
|
|
98
|
-
transport.readable.on("error", fail);
|
|
99
|
-
transport.writable.once("close", stop);
|
|
100
|
-
transport.writable.on("error", fail);
|
|
101
|
-
lifetime.signal.addEventListener("abort", stop, { once: true });
|
|
102
|
-
try {
|
|
103
|
-
if (lifetime.signal.aborted ||
|
|
104
|
-
transport.readable.readableEnded ||
|
|
105
|
-
transport.readable.destroyed ||
|
|
106
|
-
transport.writable.destroyed) {
|
|
107
|
-
stop();
|
|
108
|
-
}
|
|
109
|
-
else {
|
|
110
|
-
transport.readable.pipe(readable);
|
|
111
|
-
}
|
|
112
|
-
await connection;
|
|
113
|
-
}
|
|
114
|
-
catch (error) {
|
|
115
|
-
failure ??= error;
|
|
116
|
-
}
|
|
117
|
-
finally {
|
|
118
|
-
stop();
|
|
119
|
-
transport.readable.removeListener("end", stop);
|
|
120
|
-
transport.readable.removeListener("close", stop);
|
|
121
|
-
transport.readable.removeListener("error", fail);
|
|
122
|
-
transport.writable.removeListener("close", stop);
|
|
123
|
-
transport.writable.removeListener("error", fail);
|
|
124
|
-
lifetime.signal.removeEventListener("abort", stop);
|
|
125
|
-
readable.destroy();
|
|
126
|
-
writable.destroy();
|
|
127
|
-
}
|
|
128
|
-
try {
|
|
129
|
-
await close();
|
|
130
|
-
}
|
|
131
|
-
catch (error) {
|
|
132
|
-
if (failure !== undefined)
|
|
133
|
-
throw new AggregateError([failure, error], "MCP transport and shell cleanup failed");
|
|
134
|
-
throw error;
|
|
135
|
-
}
|
|
136
|
-
if (failure !== undefined)
|
|
137
|
-
throw failure;
|
|
138
|
-
}
|
|
139
|
-
});
|
|
140
|
-
}
|
|
141
|
-
import { PassThrough, Writable } from "node:stream";
|
|
File without changes
|