poe-code 8.0.0 → 8.0.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/index.js +15 -2
- package/dist/index.js.map +2 -2
- package/dist/metafile.json +1 -1
- package/package.json +15 -2
- package/packages/safejs/dist/chunks/chunk-AVDRLE3Y.js +3461 -0
- package/packages/safejs/dist/chunks/chunk-AVDRLE3Y.js.map +7 -0
- package/packages/safejs/dist/chunks/chunk-ME5BTAZT.js +28667 -0
- package/packages/safejs/dist/chunks/chunk-ME5BTAZT.js.map +7 -0
- package/packages/safejs/dist/cli.js +526 -491
- package/packages/safejs/dist/cli.js.map +7 -0
- package/packages/safejs/dist/core.js +13 -4
- package/packages/safejs/dist/core.js.map +7 -0
- package/packages/safejs/dist/index.js +1072 -30
- package/packages/safejs/dist/index.js.map +7 -0
|
@@ -1,30 +1,1072 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
1
|
+
import {
|
|
2
|
+
AgentSpawnError,
|
|
3
|
+
SPAWN_MODES,
|
|
4
|
+
containsPath,
|
|
5
|
+
countLineBreaks,
|
|
6
|
+
createLintModulesFromRuntimeRegistry,
|
|
7
|
+
createSpawnUsageAccumulator,
|
|
8
|
+
extractBlock,
|
|
9
|
+
formatInterpreterError,
|
|
10
|
+
isSamePath,
|
|
11
|
+
makeAgentModule,
|
|
12
|
+
makeFailModule,
|
|
13
|
+
makeFsModule,
|
|
14
|
+
makeHarnessModule,
|
|
15
|
+
makeLogModule,
|
|
16
|
+
makeMetricModule,
|
|
17
|
+
resolveCanonicalPath,
|
|
18
|
+
runWithSpawnUsageAccumulator,
|
|
19
|
+
splitFrontmatter,
|
|
20
|
+
supportsSpawnMode
|
|
21
|
+
} from "./chunks/chunk-AVDRLE3Y.js";
|
|
22
|
+
import {
|
|
23
|
+
Budget,
|
|
24
|
+
FileSnapshotBackend,
|
|
25
|
+
HostCallResumabilityError,
|
|
26
|
+
HostOperationResumePolicyError,
|
|
27
|
+
SandboxError,
|
|
28
|
+
SnapshotValidationError,
|
|
29
|
+
createReplayableRandom,
|
|
30
|
+
createSeededRandom,
|
|
31
|
+
declareHostOperation,
|
|
32
|
+
deepCopyFromSandbox,
|
|
33
|
+
deepCopyToSandbox,
|
|
34
|
+
dump,
|
|
35
|
+
hasOwnErrorCode,
|
|
36
|
+
lint,
|
|
37
|
+
noopOtelSink,
|
|
38
|
+
parse,
|
|
39
|
+
parseModule,
|
|
40
|
+
registerPendingHostCallPolicy,
|
|
41
|
+
restore,
|
|
42
|
+
run
|
|
43
|
+
} from "./chunks/chunk-ME5BTAZT.js";
|
|
44
|
+
|
|
45
|
+
// packages/safejs/src/runner/run-harness.ts
|
|
46
|
+
import { readFile, stat } from "node:fs/promises";
|
|
47
|
+
import { extname } from "node:path";
|
|
48
|
+
function getOwnEntry(record, key) {
|
|
49
|
+
return Object.prototype.hasOwnProperty.call(record, key) ? record[key] : void 0;
|
|
50
|
+
}
|
|
51
|
+
function createHarnessMeta(filepath, frontmatter) {
|
|
52
|
+
return {
|
|
53
|
+
filepath,
|
|
54
|
+
kind: getOwnEntry(frontmatter, "kind"),
|
|
55
|
+
version: getOwnEntry(frontmatter, "version")
|
|
56
|
+
};
|
|
57
|
+
}
|
|
58
|
+
function assertFrontmatterAgentModes(frontmatter) {
|
|
59
|
+
const agents = getOwnEntry(frontmatter, "agents");
|
|
60
|
+
if (typeof agents !== "object" || agents === null || Array.isArray(agents)) {
|
|
61
|
+
return;
|
|
62
|
+
}
|
|
63
|
+
for (const [name, definition] of Object.entries(agents)) {
|
|
64
|
+
if (typeof definition !== "object" || definition === null || Array.isArray(definition)) {
|
|
65
|
+
continue;
|
|
66
|
+
}
|
|
67
|
+
const record = definition;
|
|
68
|
+
const agent = getOwnEntry(record, "agent");
|
|
69
|
+
const mode = getOwnEntry(record, "mode");
|
|
70
|
+
if (typeof agent !== "string" || typeof mode !== "string") {
|
|
71
|
+
continue;
|
|
72
|
+
}
|
|
73
|
+
if (!SPAWN_MODES.includes(mode)) {
|
|
74
|
+
continue;
|
|
75
|
+
}
|
|
76
|
+
if (!supportsSpawnMode(agent, mode)) {
|
|
77
|
+
throw new Error(`Harness agent "${name}": agent "${agent}" does not support mode "${mode}".`);
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
var LintError = class extends Error {
|
|
82
|
+
diagnostics;
|
|
83
|
+
constructor(diagnostics) {
|
|
84
|
+
super(formatLintErrorMessage(diagnostics));
|
|
85
|
+
this.name = "LintError";
|
|
86
|
+
this.diagnostics = diagnostics;
|
|
87
|
+
}
|
|
88
|
+
};
|
|
89
|
+
async function runHarness(filepath, options) {
|
|
90
|
+
const rawSource = stripByteOrderMark(await readHarnessFile(filepath));
|
|
91
|
+
const { executableSource, frontmatter, isRawScript } = loadExecutableSource(filepath, rawSource);
|
|
92
|
+
assertFrontmatterAgentModes(frontmatter);
|
|
93
|
+
const meta = createHarnessMeta(filepath, frontmatter);
|
|
94
|
+
const modules = excludeHarnessModule(options.modulesFor(frontmatter, meta), isRawScript);
|
|
95
|
+
const diagnostics = lint(executableSource, {
|
|
96
|
+
filename: filepath,
|
|
97
|
+
modules: createLintModulesFromRuntimeRegistry(modules)
|
|
98
|
+
});
|
|
99
|
+
const lintErrors = diagnostics.filter((diagnostic) => diagnostic.severity === "error");
|
|
100
|
+
if (lintErrors.length > 0) {
|
|
101
|
+
throw new LintError(lintErrors);
|
|
102
|
+
}
|
|
103
|
+
return runHarnessSource(executableSource, {
|
|
104
|
+
entryPointArgs: hasDefaultExport(executableSource, filepath) ? [] : void 0,
|
|
105
|
+
filename: filepath,
|
|
106
|
+
modules,
|
|
107
|
+
otelSink: options.otelSink,
|
|
108
|
+
signal: options.signal,
|
|
109
|
+
snapshotBackend: options.snapshotBackend,
|
|
110
|
+
snapshotIntervalMs: options.snapshotIntervalMs,
|
|
111
|
+
snapshotPath: options.snapshotPath
|
|
112
|
+
});
|
|
113
|
+
}
|
|
114
|
+
async function runHarnessPair(filepath, options) {
|
|
115
|
+
const pair = resolveHarnessPair(filepath);
|
|
116
|
+
const [rawMarkdown, rawScript] = await Promise.all([
|
|
117
|
+
readHarnessFile(pair.markdownPath),
|
|
118
|
+
readHarnessFile(pair.scriptPath)
|
|
119
|
+
]);
|
|
120
|
+
const { frontmatter, body } = splitFrontmatter(stripByteOrderMark(rawMarkdown));
|
|
121
|
+
assertFrontmatterAgentModes(frontmatter);
|
|
122
|
+
const executableSource = stripByteOrderMark(rawScript);
|
|
123
|
+
const meta = createHarnessMeta(pair.markdownPath, frontmatter);
|
|
124
|
+
const modules = options.modulesFor(frontmatter, meta);
|
|
125
|
+
const diagnostics = lint(executableSource, {
|
|
126
|
+
allowedExportNames: ["schema"],
|
|
127
|
+
defaultExport: {
|
|
128
|
+
parameters: ["frontmatter"],
|
|
129
|
+
required: true
|
|
130
|
+
},
|
|
131
|
+
filename: pair.scriptPath,
|
|
132
|
+
frontmatterFields: Object.keys(frontmatter),
|
|
133
|
+
modules: createLintModulesFromRuntimeRegistry(modules)
|
|
134
|
+
});
|
|
135
|
+
const lintErrors = diagnostics.filter((diagnostic) => diagnostic.severity === "error");
|
|
136
|
+
if (lintErrors.length > 0) {
|
|
137
|
+
throw new LintError(lintErrors);
|
|
138
|
+
}
|
|
139
|
+
return runHarnessSource(executableSource, {
|
|
140
|
+
entryPointArgs: [frontmatter],
|
|
141
|
+
filename: pair.scriptPath,
|
|
142
|
+
importMeta: {
|
|
143
|
+
body,
|
|
144
|
+
filepath: pair.markdownPath,
|
|
145
|
+
kind: meta.kind,
|
|
146
|
+
version: meta.version
|
|
147
|
+
},
|
|
148
|
+
modules,
|
|
149
|
+
otelSink: options.otelSink,
|
|
150
|
+
signal: options.signal,
|
|
151
|
+
snapshotBackend: options.snapshotBackend,
|
|
152
|
+
snapshotIntervalMs: options.snapshotIntervalMs,
|
|
153
|
+
snapshotPath: options.snapshotPath
|
|
154
|
+
});
|
|
155
|
+
}
|
|
156
|
+
async function readHarnessFile(filepath) {
|
|
157
|
+
try {
|
|
158
|
+
const stats = await stat(filepath);
|
|
159
|
+
if (!stats.isFile()) {
|
|
160
|
+
throw new Error(`Harness path must point to a file: ${filepath}`);
|
|
161
|
+
}
|
|
162
|
+
return await readFile(filepath, "utf8");
|
|
163
|
+
} catch (error) {
|
|
164
|
+
if (hasOwnErrorCode(error, "ENOENT") || hasOwnErrorCode(error, "ENOTDIR")) {
|
|
165
|
+
throw new Error(`Harness file not found: ${filepath}`);
|
|
166
|
+
}
|
|
167
|
+
throw error;
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
function loadExecutableSource(filepath, source) {
|
|
171
|
+
if (isRawScriptPath(filepath)) {
|
|
172
|
+
if (source.length === 0) {
|
|
173
|
+
throw new Error(`No code block found in empty harness file: ${filepath}`);
|
|
174
|
+
}
|
|
175
|
+
return {
|
|
176
|
+
executableSource: source,
|
|
177
|
+
frontmatter: {},
|
|
178
|
+
isRawScript: true
|
|
179
|
+
};
|
|
180
|
+
}
|
|
181
|
+
const { frontmatter, body } = splitFrontmatter(source);
|
|
182
|
+
if (source.length === 0 || body.trim().length === 0) {
|
|
183
|
+
throw new Error(`No code block found in empty harness file: ${filepath}`);
|
|
184
|
+
}
|
|
185
|
+
const bodyStartOffset = source.length - body.length;
|
|
186
|
+
const { source: executableBlock, startOffset } = extractBlock(
|
|
187
|
+
body,
|
|
188
|
+
countLineBreaks(source, 0, bodyStartOffset) + 1
|
|
189
|
+
);
|
|
190
|
+
const absoluteLineOffset = countLineBreaks(source, 0, bodyStartOffset + startOffset);
|
|
191
|
+
return {
|
|
192
|
+
executableSource: createLineOffsetSource(stripHashbang(executableBlock), absoluteLineOffset),
|
|
193
|
+
frontmatter,
|
|
194
|
+
isRawScript: false
|
|
195
|
+
};
|
|
196
|
+
}
|
|
197
|
+
function resolveHarnessPair(filepath) {
|
|
198
|
+
const extension = extname(filepath);
|
|
199
|
+
const basename = extension.length === 0 ? filepath : filepath.slice(0, -extension.length);
|
|
200
|
+
if (isRawScriptExtension(extension)) {
|
|
201
|
+
return {
|
|
202
|
+
markdownPath: `${basename}.md`,
|
|
203
|
+
scriptPath: filepath
|
|
204
|
+
};
|
|
205
|
+
}
|
|
206
|
+
return {
|
|
207
|
+
markdownPath: filepath,
|
|
208
|
+
scriptPath: `${basename}.ajs`
|
|
209
|
+
};
|
|
210
|
+
}
|
|
211
|
+
function isRawScriptPath(filepath) {
|
|
212
|
+
return isRawScriptExtension(extname(filepath));
|
|
213
|
+
}
|
|
214
|
+
function isRawScriptExtension(extension) {
|
|
215
|
+
return extension === ".safejs" || extension === ".ajs";
|
|
216
|
+
}
|
|
217
|
+
async function runHarnessSource(executableSource, options) {
|
|
218
|
+
try {
|
|
219
|
+
return await run(executableSource, options);
|
|
220
|
+
} catch (error) {
|
|
221
|
+
if (isSetupError(error)) {
|
|
222
|
+
throw error;
|
|
223
|
+
}
|
|
224
|
+
return {
|
|
225
|
+
...isAbortError(error) ? { aborted: true } : {},
|
|
226
|
+
error,
|
|
227
|
+
ok: false
|
|
228
|
+
};
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
function isSetupError(error) {
|
|
232
|
+
return error instanceof Error && error.name === "ParseError";
|
|
233
|
+
}
|
|
234
|
+
function isAbortError(error) {
|
|
235
|
+
if (error instanceof Error) {
|
|
236
|
+
return error.name === "AbortError";
|
|
237
|
+
}
|
|
238
|
+
return typeof error === "object" && error !== null && Object.prototype.hasOwnProperty.call(error, "name") && error.name === "AbortError";
|
|
239
|
+
}
|
|
240
|
+
function hasDefaultExport(source, filename) {
|
|
241
|
+
return parseModule(source, filename).body.some(
|
|
242
|
+
(statement) => statement.type === "ExportDefaultDeclaration"
|
|
243
|
+
);
|
|
244
|
+
}
|
|
245
|
+
function excludeHarnessModule(modules, isRawScript) {
|
|
246
|
+
if (!isRawScript) {
|
|
247
|
+
return modules;
|
|
248
|
+
}
|
|
249
|
+
if (modules instanceof Map) {
|
|
250
|
+
const rawModules2 = new Map(modules);
|
|
251
|
+
rawModules2.delete("harness");
|
|
252
|
+
return rawModules2;
|
|
253
|
+
}
|
|
254
|
+
const rawModules = {};
|
|
255
|
+
for (const [moduleName, moduleExports] of Object.entries(modules)) {
|
|
256
|
+
if (moduleName !== "harness") {
|
|
257
|
+
rawModules[moduleName] = moduleExports;
|
|
258
|
+
}
|
|
259
|
+
}
|
|
260
|
+
return rawModules;
|
|
261
|
+
}
|
|
262
|
+
function stripByteOrderMark(source) {
|
|
263
|
+
return source.startsWith("\uFEFF") ? source.slice(1) : source;
|
|
264
|
+
}
|
|
265
|
+
function stripHashbang(source) {
|
|
266
|
+
if (!source.startsWith("#!")) {
|
|
267
|
+
return source;
|
|
268
|
+
}
|
|
269
|
+
let end = 2;
|
|
270
|
+
while (end < source.length && source[end] !== "\n" && source[end] !== "\r") end += 1;
|
|
271
|
+
return `${" ".repeat(end)}${source.slice(end)}`;
|
|
272
|
+
}
|
|
273
|
+
function createLineOffsetSource(source, lineOffset) {
|
|
274
|
+
return `${"\n".repeat(Math.max(lineOffset, 0))}${source}`;
|
|
275
|
+
}
|
|
276
|
+
function formatLintErrorMessage(diagnostics) {
|
|
277
|
+
if (diagnostics.length === 0) {
|
|
278
|
+
return "Script lint failed.";
|
|
279
|
+
}
|
|
280
|
+
return diagnostics.map(
|
|
281
|
+
(diagnostic) => `${diagnostic.filename}:${diagnostic.line}:${diagnostic.column} ${diagnostic.code} ${diagnostic.message}`
|
|
282
|
+
).join("\n");
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
// packages/safejs/src/loader/find-exported.ts
|
|
286
|
+
function findExportedConstInitializer(module, name) {
|
|
287
|
+
for (const statement of module.body) {
|
|
288
|
+
if (statement.type === "ExportDefaultDeclaration") {
|
|
289
|
+
if (name === "default") {
|
|
290
|
+
return statement.declaration;
|
|
291
|
+
}
|
|
292
|
+
continue;
|
|
293
|
+
}
|
|
294
|
+
if (statement.type !== "ExportNamedDeclaration") {
|
|
295
|
+
continue;
|
|
296
|
+
}
|
|
297
|
+
const declaration = statement.declaration;
|
|
298
|
+
if (declaration.kind !== "const") {
|
|
299
|
+
throw new Error(`Exported '${name}' must be declared with const.`);
|
|
300
|
+
}
|
|
301
|
+
if (declaration.declarations.length !== 1) {
|
|
302
|
+
throw new Error("Exported const declarations must contain exactly one declarator.");
|
|
303
|
+
}
|
|
304
|
+
const declarator = declaration.declarations[0];
|
|
305
|
+
if (declarator.id.type !== "Identifier") {
|
|
306
|
+
throw new Error("Destructured exported const bindings are not supported.");
|
|
307
|
+
}
|
|
308
|
+
if (declarator.id.name !== name) {
|
|
309
|
+
continue;
|
|
310
|
+
}
|
|
311
|
+
if (declarator.init === void 0) {
|
|
312
|
+
throw new Error(`Exported const '${name}' must have an initializer.`);
|
|
313
|
+
}
|
|
314
|
+
return declarator.init;
|
|
315
|
+
}
|
|
316
|
+
return void 0;
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
// packages/safejs/src/modules/env.ts
|
|
320
|
+
function makeEnvModule(allowList) {
|
|
321
|
+
const allowedNames = normalizeAllowList(allowList);
|
|
322
|
+
return {
|
|
323
|
+
get(name) {
|
|
324
|
+
const normalizedName = readNonEmptyString(name, "Environment variable name");
|
|
325
|
+
if (!allowedNames.has(normalizedName)) {
|
|
326
|
+
return void 0;
|
|
327
|
+
}
|
|
328
|
+
return process.env[normalizedName];
|
|
329
|
+
}
|
|
330
|
+
};
|
|
331
|
+
}
|
|
332
|
+
function normalizeAllowList(allowList) {
|
|
333
|
+
if (!Array.isArray(allowList)) {
|
|
334
|
+
throw new Error("Environment allow list must be an array of non-empty strings.");
|
|
335
|
+
}
|
|
336
|
+
return new Set(
|
|
337
|
+
allowList.map((entry, index) => readNonEmptyString(entry, `Environment allow list[${index}]`))
|
|
338
|
+
);
|
|
339
|
+
}
|
|
340
|
+
function readNonEmptyString(value, label) {
|
|
341
|
+
if (typeof value !== "string") {
|
|
342
|
+
throw new Error(`${label} must be a non-empty string.`);
|
|
343
|
+
}
|
|
344
|
+
const normalizedValue = value.trim();
|
|
345
|
+
if (normalizedValue.length === 0) {
|
|
346
|
+
throw new Error(`${label} must be a non-empty string.`);
|
|
347
|
+
}
|
|
348
|
+
return normalizedValue;
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
// packages/safejs/src/modules/git.ts
|
|
352
|
+
import { execFile } from "node:child_process";
|
|
353
|
+
import { mkdir, readlink, realpath, rm, stat as stat2 } from "node:fs/promises";
|
|
354
|
+
import { dirname, isAbsolute, resolve } from "node:path";
|
|
355
|
+
var GIT_EXEC_MAX_BUFFER = 10 * 1024 * 1024;
|
|
356
|
+
var SAVEPOINT_REF_PREFIX = "refs/poe-code/checkpoints/";
|
|
357
|
+
function makeGitModule(cwd) {
|
|
358
|
+
const normalizedCwd = readNonEmptyString2(cwd, "Git module cwd");
|
|
359
|
+
return {
|
|
360
|
+
async head() {
|
|
361
|
+
return runGitAndTrim(normalizedCwd, ["rev-parse", "HEAD"]);
|
|
362
|
+
},
|
|
363
|
+
async checkpoint() {
|
|
364
|
+
const currentHead = await runGitAndTrim(normalizedCwd, ["rev-parse", "HEAD"]);
|
|
365
|
+
const status = await runGitAndTrim(normalizedCwd, ["status", "--porcelain"]);
|
|
366
|
+
if (status.length === 0) {
|
|
367
|
+
return {
|
|
368
|
+
head: currentHead
|
|
369
|
+
};
|
|
370
|
+
}
|
|
371
|
+
const stashRef = createSavepointRef();
|
|
372
|
+
const stashMessage = `poe-code checkpoint ${stashRef}`;
|
|
373
|
+
await runGit(normalizedCwd, [
|
|
374
|
+
"stash",
|
|
375
|
+
"push",
|
|
376
|
+
"--include-untracked",
|
|
377
|
+
"--message",
|
|
378
|
+
stashMessage
|
|
379
|
+
]);
|
|
380
|
+
try {
|
|
381
|
+
const stashOid = await runGitAndTrim(normalizedCwd, ["rev-parse", "stash@{0}"]);
|
|
382
|
+
await runGit(normalizedCwd, ["update-ref", stashRef, stashOid]);
|
|
383
|
+
await runGit(normalizedCwd, ["stash", "apply", "--index", stashRef]);
|
|
384
|
+
await runGit(normalizedCwd, ["stash", "drop", "stash@{0}"]);
|
|
385
|
+
return {
|
|
386
|
+
head: currentHead,
|
|
387
|
+
stashRef
|
|
388
|
+
};
|
|
389
|
+
} catch (error) {
|
|
390
|
+
await cleanupFailedCheckpoint(normalizedCwd, stashRef);
|
|
391
|
+
throw error;
|
|
392
|
+
}
|
|
393
|
+
},
|
|
394
|
+
async commit(options) {
|
|
395
|
+
const normalizedOptions = normalizeCommitOptions(options);
|
|
396
|
+
if (normalizedOptions.files === void 0) {
|
|
397
|
+
await runGit(normalizedCwd, ["add", "--all"]);
|
|
398
|
+
await runGit(normalizedCwd, ["commit", "--message", normalizedOptions.message]);
|
|
399
|
+
} else {
|
|
400
|
+
await runGit(normalizedCwd, ["add", "--", ...normalizedOptions.files]);
|
|
401
|
+
await runGit(normalizedCwd, [
|
|
402
|
+
"commit",
|
|
403
|
+
"--message",
|
|
404
|
+
normalizedOptions.message,
|
|
405
|
+
"--",
|
|
406
|
+
...normalizedOptions.files
|
|
407
|
+
]);
|
|
408
|
+
}
|
|
409
|
+
return runGitAndTrim(normalizedCwd, ["rev-parse", "HEAD"]);
|
|
410
|
+
},
|
|
411
|
+
async revert(savepoint) {
|
|
412
|
+
const normalizedSavepoint = normalizeSavepoint(savepoint);
|
|
413
|
+
try {
|
|
414
|
+
await runGit(normalizedCwd, ["reset", "--hard", normalizedSavepoint.head]);
|
|
415
|
+
await runGit(normalizedCwd, ["clean", "--force", "-d"]);
|
|
416
|
+
if (normalizedSavepoint.stashRef !== void 0) {
|
|
417
|
+
await runGit(normalizedCwd, ["stash", "apply", "--index", normalizedSavepoint.stashRef]);
|
|
418
|
+
}
|
|
419
|
+
} finally {
|
|
420
|
+
if (normalizedSavepoint.stashRef !== void 0) {
|
|
421
|
+
await deleteSavepointRef(normalizedCwd, normalizedSavepoint.stashRef);
|
|
422
|
+
}
|
|
423
|
+
}
|
|
424
|
+
},
|
|
425
|
+
async diff() {
|
|
426
|
+
const { stdout } = await runGit(normalizedCwd, ["diff", "HEAD", "--"]);
|
|
427
|
+
return stdout;
|
|
428
|
+
},
|
|
429
|
+
async worktreeCreate(branch, options) {
|
|
430
|
+
const normalizedBranch = readNonEmptyString2(branch, "Git worktree branch");
|
|
431
|
+
const normalizedOptions = normalizeWorktreeCreateOptions(options);
|
|
432
|
+
const repoRoot = await getRepoRoot(normalizedCwd);
|
|
433
|
+
const worktreePath = await resolveWorktreePath(
|
|
434
|
+
repoRoot,
|
|
435
|
+
normalizedOptions.path ?? createDefaultWorktreePath(repoRoot, normalizedBranch),
|
|
436
|
+
"Git worktree path"
|
|
437
|
+
);
|
|
438
|
+
if (await gitRefExists(normalizedCwd, `refs/heads/${normalizedBranch}`)) {
|
|
439
|
+
throw new Error(`Git worktree branch '${normalizedBranch}' already exists.`);
|
|
440
|
+
}
|
|
441
|
+
await mkdir(dirname(worktreePath), { recursive: true });
|
|
442
|
+
try {
|
|
443
|
+
await runGit(normalizedCwd, [
|
|
444
|
+
"worktree",
|
|
445
|
+
"add",
|
|
446
|
+
"-b",
|
|
447
|
+
normalizedBranch,
|
|
448
|
+
worktreePath,
|
|
449
|
+
normalizedOptions.base
|
|
450
|
+
]);
|
|
451
|
+
} catch (error) {
|
|
452
|
+
if (isBranchAlreadyExistsError(error, normalizedBranch)) {
|
|
453
|
+
throw new Error(`Git worktree branch '${normalizedBranch}' already exists.`);
|
|
454
|
+
}
|
|
455
|
+
throw error;
|
|
456
|
+
}
|
|
457
|
+
return {
|
|
458
|
+
path: worktreePath,
|
|
459
|
+
branch: normalizedBranch
|
|
460
|
+
};
|
|
461
|
+
},
|
|
462
|
+
async worktreeRemove(path) {
|
|
463
|
+
const repoRoot = await getRepoRoot(normalizedCwd);
|
|
464
|
+
const worktreePath = await resolveWorktreePath(repoRoot, path, "Git worktree path");
|
|
465
|
+
const worktrees = await listWorktrees(normalizedCwd);
|
|
466
|
+
if (!worktrees.some((worktree) => resolve(worktree.path) === worktreePath)) {
|
|
467
|
+
return;
|
|
468
|
+
}
|
|
469
|
+
await runGit(normalizedCwd, ["worktree", "remove", "--force", worktreePath]);
|
|
470
|
+
await rm(worktreePath, { recursive: true, force: true });
|
|
471
|
+
},
|
|
472
|
+
async worktreeList() {
|
|
473
|
+
return listWorktrees(normalizedCwd);
|
|
474
|
+
}
|
|
475
|
+
};
|
|
476
|
+
}
|
|
477
|
+
function normalizeCommitOptions(options) {
|
|
478
|
+
if (!isRecord(options)) {
|
|
479
|
+
throw new Error("Git commit options must be an object.");
|
|
480
|
+
}
|
|
481
|
+
const files = getOwnProperty(options, "files");
|
|
482
|
+
return {
|
|
483
|
+
message: readNonEmptyString2(getOwnProperty(options, "message"), "Git commit options message"),
|
|
484
|
+
files: files === void 0 ? void 0 : readNonEmptyStringArray(files, "Git commit options files")
|
|
485
|
+
};
|
|
486
|
+
}
|
|
487
|
+
function normalizeSavepoint(savepoint) {
|
|
488
|
+
if (!isRecord(savepoint)) {
|
|
489
|
+
throw new Error("Git savepoint must be an object.");
|
|
490
|
+
}
|
|
491
|
+
const stashRefValue = getOwnProperty(savepoint, "stashRef");
|
|
492
|
+
const stashRef = stashRefValue === void 0 ? void 0 : readNonEmptyString2(stashRefValue, "Git savepoint stashRef");
|
|
493
|
+
if (stashRef !== void 0 && !stashRef.startsWith(SAVEPOINT_REF_PREFIX)) {
|
|
494
|
+
throw new Error("Git savepoint stashRef must be a Poe checkpoint ref.");
|
|
495
|
+
}
|
|
496
|
+
return {
|
|
497
|
+
head: readNonEmptyString2(getOwnProperty(savepoint, "head"), "Git savepoint head"),
|
|
498
|
+
stashRef
|
|
499
|
+
};
|
|
500
|
+
}
|
|
501
|
+
function normalizeWorktreeCreateOptions(options) {
|
|
502
|
+
if (options === void 0) {
|
|
503
|
+
return {
|
|
504
|
+
base: "HEAD"
|
|
505
|
+
};
|
|
506
|
+
}
|
|
507
|
+
if (!isRecord(options)) {
|
|
508
|
+
throw new Error("Git worktree create options must be an object.");
|
|
509
|
+
}
|
|
510
|
+
const base = getOwnProperty(options, "base");
|
|
511
|
+
const path = getOwnProperty(options, "path");
|
|
512
|
+
return {
|
|
513
|
+
base: base === void 0 ? "HEAD" : readNonEmptyString2(base, "Git worktree create options base"),
|
|
514
|
+
path: path === void 0 ? void 0 : readNonEmptyString2(path, "Git worktree create options path")
|
|
515
|
+
};
|
|
516
|
+
}
|
|
517
|
+
function createSavepointRef() {
|
|
518
|
+
return `${SAVEPOINT_REF_PREFIX}${Date.now().toString(36)}-${Math.random().toString(36).slice(2, 10)}`;
|
|
519
|
+
}
|
|
520
|
+
function createDefaultWorktreePath(repoRoot, branch) {
|
|
521
|
+
return resolve(repoRoot, ".poe-code", "worktrees", createSafeBranchDirectoryName(branch));
|
|
522
|
+
}
|
|
523
|
+
function createSafeBranchDirectoryName(branch) {
|
|
524
|
+
return encodeURIComponent(branch);
|
|
525
|
+
}
|
|
526
|
+
async function getRepoRoot(cwd) {
|
|
527
|
+
return runGitAndTrim(cwd, ["rev-parse", "--show-toplevel"]);
|
|
528
|
+
}
|
|
529
|
+
async function gitRefExists(cwd, ref) {
|
|
530
|
+
try {
|
|
531
|
+
await runGit(cwd, ["show-ref", "--verify", "--quiet", ref]);
|
|
532
|
+
return true;
|
|
533
|
+
} catch (error) {
|
|
534
|
+
void error;
|
|
535
|
+
return false;
|
|
536
|
+
}
|
|
537
|
+
}
|
|
538
|
+
async function listWorktrees(cwd) {
|
|
539
|
+
const { stdout } = await runGit(cwd, ["worktree", "list", "--porcelain"]);
|
|
540
|
+
return parseWorktreeList(stdout);
|
|
541
|
+
}
|
|
542
|
+
function parseWorktreeList(output) {
|
|
543
|
+
const worktrees = [];
|
|
544
|
+
let current = {};
|
|
545
|
+
const finishCurrent = () => {
|
|
546
|
+
if (current.path !== void 0 && current.branch !== void 0) {
|
|
547
|
+
worktrees.push({
|
|
548
|
+
path: current.path,
|
|
549
|
+
branch: current.branch
|
|
550
|
+
});
|
|
551
|
+
}
|
|
552
|
+
current = {};
|
|
553
|
+
};
|
|
554
|
+
for (const line of output.split("\n")) {
|
|
555
|
+
if (line.length === 0) {
|
|
556
|
+
finishCurrent();
|
|
557
|
+
continue;
|
|
558
|
+
}
|
|
559
|
+
if (line.startsWith("worktree ")) {
|
|
560
|
+
current.path = line.slice("worktree ".length);
|
|
561
|
+
continue;
|
|
562
|
+
}
|
|
563
|
+
if (line.startsWith("branch ")) {
|
|
564
|
+
current.branch = parseBranchRef(line.slice("branch ".length));
|
|
565
|
+
}
|
|
566
|
+
}
|
|
567
|
+
finishCurrent();
|
|
568
|
+
return worktrees;
|
|
569
|
+
}
|
|
570
|
+
function parseBranchRef(ref) {
|
|
571
|
+
const branchPrefix = "refs/heads/";
|
|
572
|
+
return ref.startsWith(branchPrefix) ? ref.slice(branchPrefix.length) : ref;
|
|
573
|
+
}
|
|
574
|
+
async function resolveWorktreePath(repoRoot, path, label) {
|
|
575
|
+
const resolvedRoot = resolve(repoRoot);
|
|
576
|
+
const resolvedPath = isAbsolute(path) ? resolve(path) : resolve(resolvedRoot, path);
|
|
577
|
+
const canonicalRoot = await realpath(resolvedRoot);
|
|
578
|
+
const canonicalPath = await resolveCanonicalPath({ realpath, readlink }, resolvedPath);
|
|
579
|
+
const [inside, isRootItself] = await Promise.all([
|
|
580
|
+
containsPath(stat2, canonicalRoot, canonicalPath),
|
|
581
|
+
isSamePath(stat2, canonicalPath, canonicalRoot)
|
|
582
|
+
]);
|
|
583
|
+
if (!inside || isRootItself) {
|
|
584
|
+
throw new Error(`${label} must be inside the git repository.`);
|
|
585
|
+
}
|
|
586
|
+
return canonicalPath;
|
|
587
|
+
}
|
|
588
|
+
function isBranchAlreadyExistsError(error, branch) {
|
|
589
|
+
return error instanceof Error && error.message.includes(branch) && error.message.includes("already exists");
|
|
590
|
+
}
|
|
591
|
+
async function runGitAndTrim(cwd, args) {
|
|
592
|
+
const { stdout } = await runGit(cwd, args);
|
|
593
|
+
return stdout.trim();
|
|
594
|
+
}
|
|
595
|
+
async function runGit(cwd, args) {
|
|
596
|
+
return new Promise((resolve2, reject) => {
|
|
597
|
+
execFile(
|
|
598
|
+
"git",
|
|
599
|
+
args,
|
|
600
|
+
{
|
|
601
|
+
cwd,
|
|
602
|
+
encoding: "utf8",
|
|
603
|
+
maxBuffer: GIT_EXEC_MAX_BUFFER
|
|
604
|
+
},
|
|
605
|
+
(error, stdout, stderr) => {
|
|
606
|
+
if (error) {
|
|
607
|
+
reject(
|
|
608
|
+
new Error(
|
|
609
|
+
`${formatGitCommand(args)} failed: ${pickGitFailureMessage(stdout, stderr, error)}`
|
|
610
|
+
)
|
|
611
|
+
);
|
|
612
|
+
return;
|
|
613
|
+
}
|
|
614
|
+
resolve2({
|
|
615
|
+
stdout,
|
|
616
|
+
stderr
|
|
617
|
+
});
|
|
618
|
+
}
|
|
619
|
+
);
|
|
620
|
+
});
|
|
621
|
+
}
|
|
622
|
+
async function cleanupFailedCheckpoint(cwd, stashRef) {
|
|
623
|
+
await deleteSavepointRef(cwd, stashRef);
|
|
624
|
+
const restored = await tryRunGit(cwd, ["stash", "apply", "--index", "stash@{0}"]);
|
|
625
|
+
if (restored) {
|
|
626
|
+
await tryRunGit(cwd, ["stash", "drop", "stash@{0}"]);
|
|
627
|
+
}
|
|
628
|
+
}
|
|
629
|
+
async function deleteSavepointRef(cwd, stashRef) {
|
|
630
|
+
await tryRunGit(cwd, ["update-ref", "--delete", stashRef]);
|
|
631
|
+
}
|
|
632
|
+
async function tryRunGit(cwd, args) {
|
|
633
|
+
try {
|
|
634
|
+
await runGit(cwd, args);
|
|
635
|
+
return true;
|
|
636
|
+
} catch (error) {
|
|
637
|
+
void error;
|
|
638
|
+
return false;
|
|
639
|
+
}
|
|
640
|
+
}
|
|
641
|
+
function formatGitCommand(args) {
|
|
642
|
+
return ["git", ...args].join(" ");
|
|
643
|
+
}
|
|
644
|
+
function pickGitFailureMessage(stdout, stderr, error) {
|
|
645
|
+
const stderrText = stderr.trim();
|
|
646
|
+
if (stderrText.length > 0) {
|
|
647
|
+
return stderrText;
|
|
648
|
+
}
|
|
649
|
+
const stdoutText = stdout.trim();
|
|
650
|
+
if (stdoutText.length > 0) {
|
|
651
|
+
return stdoutText;
|
|
652
|
+
}
|
|
653
|
+
return error.message;
|
|
654
|
+
}
|
|
655
|
+
function readNonEmptyString2(value, label) {
|
|
656
|
+
if (typeof value !== "string" || value.trim().length === 0) {
|
|
657
|
+
throw new Error(`${label} must be a non-empty string.`);
|
|
658
|
+
}
|
|
659
|
+
return value;
|
|
660
|
+
}
|
|
661
|
+
function readNonEmptyStringArray(value, label) {
|
|
662
|
+
if (!Array.isArray(value) || value.length === 0) {
|
|
663
|
+
throw new Error(`${label} must be a non-empty array of strings.`);
|
|
664
|
+
}
|
|
665
|
+
const entries = value.map((entry, index) => readNonEmptyString2(entry, `${label}[${index}]`));
|
|
666
|
+
return [...entries];
|
|
667
|
+
}
|
|
668
|
+
function isRecord(value) {
|
|
669
|
+
return typeof value === "object" && value !== null;
|
|
670
|
+
}
|
|
671
|
+
function getOwnProperty(value, name) {
|
|
672
|
+
return hasOwnProperty(value, name) ? value[name] : void 0;
|
|
673
|
+
}
|
|
674
|
+
function hasOwnProperty(value, name) {
|
|
675
|
+
return Object.prototype.hasOwnProperty.call(value, name);
|
|
676
|
+
}
|
|
677
|
+
|
|
678
|
+
// packages/safejs/src/modules/mcp.ts
|
|
679
|
+
var DEFAULT_TOOL_BATCH_CONCURRENCY = 4;
|
|
680
|
+
function makeMcpModule(connectMcp) {
|
|
681
|
+
return {
|
|
682
|
+
server(server) {
|
|
683
|
+
return normalizeServerHandle(server);
|
|
684
|
+
},
|
|
685
|
+
async client(handle) {
|
|
686
|
+
const connection = validateConnection(await connectMcp(normalizeServerHandle(handle)));
|
|
687
|
+
return {
|
|
688
|
+
async tools() {
|
|
689
|
+
return normalizeToolsResult(await connection.listTools());
|
|
690
|
+
},
|
|
691
|
+
async tool(name, args) {
|
|
692
|
+
return connection.callTool(
|
|
693
|
+
createNullRecord({
|
|
694
|
+
name: readNonEmptyTrimmedString(name, "MCP tool name"),
|
|
695
|
+
...args === void 0 ? {} : { arguments: readToolArguments(args) }
|
|
696
|
+
})
|
|
697
|
+
);
|
|
698
|
+
},
|
|
699
|
+
async toolBatch(calls) {
|
|
700
|
+
return executeToolBatch(connection, readToolBatchCalls(calls));
|
|
701
|
+
}
|
|
702
|
+
};
|
|
703
|
+
}
|
|
704
|
+
};
|
|
705
|
+
}
|
|
706
|
+
async function executeToolBatch(connection, calls) {
|
|
707
|
+
if (calls.length === 0) {
|
|
708
|
+
return [];
|
|
709
|
+
}
|
|
710
|
+
if (connection.callToolBatch !== void 0 && calls.every((call) => call.ok)) {
|
|
711
|
+
try {
|
|
712
|
+
return normalizeProtocolBatchResults(
|
|
713
|
+
await connection.callToolBatch(calls.map((call) => call.request)),
|
|
714
|
+
calls.length
|
|
715
|
+
);
|
|
716
|
+
} catch (error) {
|
|
717
|
+
const batchError = normalizeToolBatchError(error);
|
|
718
|
+
return calls.map(
|
|
719
|
+
() => createNullRecord({
|
|
720
|
+
ok: false,
|
|
721
|
+
error: batchError
|
|
722
|
+
})
|
|
723
|
+
);
|
|
724
|
+
}
|
|
725
|
+
}
|
|
726
|
+
return executeToolBatchWithConcurrency(connection, calls, DEFAULT_TOOL_BATCH_CONCURRENCY);
|
|
727
|
+
}
|
|
728
|
+
async function executeToolBatchWithConcurrency(connection, calls, concurrency) {
|
|
729
|
+
const results = new Array(calls.length);
|
|
730
|
+
let nextIndex = 0;
|
|
731
|
+
let disconnectError;
|
|
732
|
+
const workerCount = Math.min(concurrency, calls.length);
|
|
733
|
+
const workers = Array.from({ length: workerCount }, async () => {
|
|
734
|
+
while (nextIndex < calls.length) {
|
|
735
|
+
const index = nextIndex;
|
|
736
|
+
nextIndex += 1;
|
|
737
|
+
const call = calls[index];
|
|
738
|
+
if (call === void 0) {
|
|
739
|
+
continue;
|
|
740
|
+
}
|
|
741
|
+
if (!call.ok) {
|
|
742
|
+
results[index] = createNullRecord({
|
|
743
|
+
ok: false,
|
|
744
|
+
error: call.error
|
|
745
|
+
});
|
|
746
|
+
continue;
|
|
747
|
+
}
|
|
748
|
+
if (disconnectError !== void 0) {
|
|
749
|
+
results[index] = createNullRecord({
|
|
750
|
+
ok: false,
|
|
751
|
+
error: disconnectError
|
|
752
|
+
});
|
|
753
|
+
continue;
|
|
754
|
+
}
|
|
755
|
+
try {
|
|
756
|
+
results[index] = createNullRecord({
|
|
757
|
+
ok: true,
|
|
758
|
+
value: await connection.callTool(call.request)
|
|
759
|
+
});
|
|
760
|
+
} catch (error) {
|
|
761
|
+
const toolError = normalizeToolBatchError(error);
|
|
762
|
+
results[index] = createNullRecord({
|
|
763
|
+
ok: false,
|
|
764
|
+
error: toolError
|
|
765
|
+
});
|
|
766
|
+
if (isMcpDisconnectError(toolError)) {
|
|
767
|
+
disconnectError = toolError;
|
|
768
|
+
}
|
|
769
|
+
}
|
|
770
|
+
}
|
|
771
|
+
});
|
|
772
|
+
await Promise.all(workers);
|
|
773
|
+
return results;
|
|
774
|
+
}
|
|
775
|
+
function normalizeProtocolBatchResults(value, expectedLength) {
|
|
776
|
+
if (!Array.isArray(value) || value.length !== expectedLength) {
|
|
777
|
+
throw new Error(
|
|
778
|
+
"MCP callToolBatch() must resolve to a result array matching the input length."
|
|
779
|
+
);
|
|
780
|
+
}
|
|
781
|
+
return value.map((entry, index) => normalizeProtocolBatchResult(entry, index));
|
|
782
|
+
}
|
|
783
|
+
function normalizeProtocolBatchResult(value, index) {
|
|
784
|
+
if (!isRecord2(value)) {
|
|
785
|
+
throw new Error(`MCP callToolBatch()[${index}] must be a result envelope.`);
|
|
786
|
+
}
|
|
787
|
+
const ok = getOwnProperty2(value, "ok");
|
|
788
|
+
if (typeof ok !== "boolean") {
|
|
789
|
+
throw new Error(`MCP callToolBatch()[${index}] must be a result envelope.`);
|
|
790
|
+
}
|
|
791
|
+
if (ok === true) {
|
|
792
|
+
return createNullRecord({
|
|
793
|
+
ok: true,
|
|
794
|
+
value: getOwnProperty2(value, "value")
|
|
795
|
+
});
|
|
796
|
+
}
|
|
797
|
+
return createNullRecord({
|
|
798
|
+
ok: false,
|
|
799
|
+
error: normalizeToolBatchError(getOwnProperty2(value, "error"))
|
|
800
|
+
});
|
|
801
|
+
}
|
|
802
|
+
function normalizeServerHandle(value) {
|
|
803
|
+
if (!isRecord2(value)) {
|
|
804
|
+
throw new Error("MCP server must be an object.");
|
|
805
|
+
}
|
|
806
|
+
const command = getOwnProperty2(value, "command");
|
|
807
|
+
const args = getOwnProperty2(value, "args");
|
|
808
|
+
const env = getOwnProperty2(value, "env");
|
|
809
|
+
return createNullRecord({
|
|
810
|
+
command: readNonEmptyTrimmedString(command, "MCP server command"),
|
|
811
|
+
...args === void 0 ? {} : { args: readStringArray(args, "MCP server args") },
|
|
812
|
+
...env === void 0 ? {} : { env: readStringRecord(env, "MCP server env") }
|
|
813
|
+
});
|
|
814
|
+
}
|
|
815
|
+
function validateConnection(value) {
|
|
816
|
+
if (!isRecord2(value) || typeof value.listTools !== "function" || typeof value.callTool !== "function") {
|
|
817
|
+
throw new Error("connectMcp must resolve to an object with listTools() and callTool().");
|
|
818
|
+
}
|
|
819
|
+
const callToolBatch = getOwnProperty2(value, "callToolBatch");
|
|
820
|
+
return createNullRecord({
|
|
821
|
+
listTools: () => value.listTools(),
|
|
822
|
+
callTool: (params) => value.callTool(params),
|
|
823
|
+
...typeof callToolBatch === "function" ? {
|
|
824
|
+
callToolBatch: (params) => callToolBatch(params)
|
|
825
|
+
} : {}
|
|
826
|
+
});
|
|
827
|
+
}
|
|
828
|
+
function normalizeToolsResult(value) {
|
|
829
|
+
const tools = isRecord2(value) ? getOwnProperty2(value, "tools") : void 0;
|
|
830
|
+
if (!Array.isArray(tools)) {
|
|
831
|
+
throw new Error("MCP listTools() must resolve to an object with a tools array.");
|
|
832
|
+
}
|
|
833
|
+
return tools.map((tool, index) => normalizeTool(tool, index));
|
|
834
|
+
}
|
|
835
|
+
function normalizeTool(value, index) {
|
|
836
|
+
if (!isRecord2(value)) {
|
|
837
|
+
throw new Error(`MCP tool[${index}] must be an object.`);
|
|
838
|
+
}
|
|
839
|
+
const description = getOwnProperty2(value, "description");
|
|
840
|
+
const inputSchema = getOwnProperty2(value, "inputSchema");
|
|
841
|
+
return createNullRecord({
|
|
842
|
+
name: readNonEmptyString3(getOwnProperty2(value, "name"), `MCP tool[${index}] name`),
|
|
843
|
+
...description === void 0 ? {} : { description: readOptionalString(description, `MCP tool[${index}] description`) },
|
|
844
|
+
...inputSchema === void 0 ? {} : { schema: inputSchema }
|
|
845
|
+
});
|
|
846
|
+
}
|
|
847
|
+
function readStringArray(value, label) {
|
|
848
|
+
if (!Array.isArray(value) || value.some((entry) => typeof entry !== "string")) {
|
|
849
|
+
throw new Error(`${label} must be an array of strings.`);
|
|
850
|
+
}
|
|
851
|
+
return [...value];
|
|
852
|
+
}
|
|
853
|
+
function readStringRecord(value, label) {
|
|
854
|
+
if (!isRecord2(value)) {
|
|
855
|
+
throw new Error(`${label} must be an object.`);
|
|
856
|
+
}
|
|
857
|
+
const entries = Object.entries(value);
|
|
858
|
+
if (entries.some(([, entry]) => typeof entry !== "string")) {
|
|
859
|
+
throw new Error(`${label} must be a string record.`);
|
|
860
|
+
}
|
|
861
|
+
return createNullRecord(Object.fromEntries(entries));
|
|
862
|
+
}
|
|
863
|
+
function readOptionalString(value, label) {
|
|
864
|
+
if (value === void 0) {
|
|
865
|
+
return void 0;
|
|
866
|
+
}
|
|
867
|
+
if (typeof value !== "string") {
|
|
868
|
+
throw new Error(`${label} must be a string.`);
|
|
869
|
+
}
|
|
870
|
+
return value;
|
|
871
|
+
}
|
|
872
|
+
function readNonEmptyString3(value, label) {
|
|
873
|
+
const text = readOptionalString(value, label);
|
|
874
|
+
if (text === void 0 || text.trim().length === 0) {
|
|
875
|
+
throw new Error(`${label} must be a non-empty string.`);
|
|
876
|
+
}
|
|
877
|
+
return text;
|
|
878
|
+
}
|
|
879
|
+
function readNonEmptyTrimmedString(value, label) {
|
|
880
|
+
return readNonEmptyString3(value, label).trim();
|
|
881
|
+
}
|
|
882
|
+
function readToolArguments(value) {
|
|
883
|
+
if (!isPlainObjectRecord(value)) {
|
|
884
|
+
throw new Error("MCP tool arguments must be an object.");
|
|
885
|
+
}
|
|
886
|
+
return createNullRecord(Object.fromEntries(Object.entries(value)));
|
|
887
|
+
}
|
|
888
|
+
function readToolBatchCalls(value) {
|
|
889
|
+
if (!Array.isArray(value)) {
|
|
890
|
+
throw new Error("MCP toolBatch calls must be an array.");
|
|
891
|
+
}
|
|
892
|
+
return value.map((entry, index) => normalizeToolBatchCall(entry, index));
|
|
893
|
+
}
|
|
894
|
+
function normalizeToolBatchCall(value, index) {
|
|
895
|
+
try {
|
|
896
|
+
if (!isRecord2(value)) {
|
|
897
|
+
throw new Error(`MCP toolBatch call[${index}] must be an object.`);
|
|
898
|
+
}
|
|
899
|
+
const args = getOwnProperty2(value, "args");
|
|
900
|
+
return createNullRecord({
|
|
901
|
+
ok: true,
|
|
902
|
+
request: createNullRecord({
|
|
903
|
+
name: readNonEmptyTrimmedString(
|
|
904
|
+
getOwnProperty2(value, "name"),
|
|
905
|
+
`MCP toolBatch call[${index}] name`
|
|
906
|
+
),
|
|
907
|
+
...args === void 0 ? {} : { arguments: readToolArguments(args) }
|
|
908
|
+
})
|
|
909
|
+
});
|
|
910
|
+
} catch (error) {
|
|
911
|
+
return createNullRecord({
|
|
912
|
+
ok: false,
|
|
913
|
+
error: normalizeToolBatchError(error)
|
|
914
|
+
});
|
|
915
|
+
}
|
|
916
|
+
}
|
|
917
|
+
function normalizeToolBatchError(error) {
|
|
918
|
+
if (error instanceof Error) {
|
|
919
|
+
return createNullRecord({
|
|
920
|
+
name: error.name.length === 0 ? "Error" : error.name,
|
|
921
|
+
message: error.message
|
|
922
|
+
});
|
|
923
|
+
}
|
|
924
|
+
if (isRecord2(error)) {
|
|
925
|
+
const nameValue = getOwnProperty2(error, "name");
|
|
926
|
+
const messageValue = getOwnProperty2(error, "message");
|
|
927
|
+
const name = typeof nameValue === "string" && nameValue.trim().length > 0 ? nameValue : "Error";
|
|
928
|
+
const message = typeof messageValue === "string" ? messageValue : String(error);
|
|
929
|
+
return createNullRecord({
|
|
930
|
+
name,
|
|
931
|
+
message
|
|
932
|
+
});
|
|
933
|
+
}
|
|
934
|
+
return createNullRecord({
|
|
935
|
+
name: "Error",
|
|
936
|
+
message: error === void 0 ? "" : String(error)
|
|
937
|
+
});
|
|
938
|
+
}
|
|
939
|
+
function isMcpDisconnectError(error) {
|
|
940
|
+
const message = error.message.toLowerCase();
|
|
941
|
+
return message.includes("disconnected") || message.includes("connection closed") || message.includes("transport closed") || message.includes("disposed") || message.includes("eof");
|
|
942
|
+
}
|
|
943
|
+
function isRecord2(value) {
|
|
944
|
+
return typeof value === "object" && value !== null;
|
|
945
|
+
}
|
|
946
|
+
function getOwnProperty2(value, name) {
|
|
947
|
+
return hasOwnProperty2(value, name) ? value[name] : void 0;
|
|
948
|
+
}
|
|
949
|
+
function hasOwnProperty2(value, name) {
|
|
950
|
+
return Object.prototype.hasOwnProperty.call(value, name);
|
|
951
|
+
}
|
|
952
|
+
function createNullRecord(value) {
|
|
953
|
+
return Object.assign(/* @__PURE__ */ Object.create(null), value);
|
|
954
|
+
}
|
|
955
|
+
function isPlainObjectRecord(value) {
|
|
956
|
+
if (!isRecord2(value) || Array.isArray(value)) {
|
|
957
|
+
return false;
|
|
958
|
+
}
|
|
959
|
+
const prototype = Object.getPrototypeOf(value);
|
|
960
|
+
return prototype === Object.prototype || prototype === null;
|
|
961
|
+
}
|
|
962
|
+
|
|
963
|
+
// packages/safejs/src/modules/time.ts
|
|
964
|
+
function makeTimeModule(options = {}) {
|
|
965
|
+
const hasDeterministicRandom = options.random !== void 0 || options.seed !== void 0;
|
|
966
|
+
const random = options.random ?? (options.seed === void 0 ? () => Math.random() : createSeededRandom(options.seed).next);
|
|
967
|
+
const now = options.now ?? (() => Date.now());
|
|
968
|
+
const signal = options.signal;
|
|
969
|
+
async function sleep(ms) {
|
|
970
|
+
assertSleepDelay(ms);
|
|
971
|
+
return await new Promise((resolve2, reject) => {
|
|
972
|
+
if (signal?.aborted === true) {
|
|
973
|
+
reject(createSleepAbortError());
|
|
974
|
+
return;
|
|
975
|
+
}
|
|
976
|
+
const start = performance.now();
|
|
977
|
+
let timeout = setTimeout(resolveSleep, ms);
|
|
978
|
+
function cleanup() {
|
|
979
|
+
clearTimeout(timeout);
|
|
980
|
+
signal?.removeEventListener("abort", rejectSleep);
|
|
981
|
+
}
|
|
982
|
+
function resolveSleep() {
|
|
983
|
+
const remainingMs = ms - (performance.now() - start);
|
|
984
|
+
if (remainingMs > 0) {
|
|
985
|
+
timeout = setTimeout(resolveSleep, remainingMs);
|
|
986
|
+
return;
|
|
987
|
+
}
|
|
988
|
+
cleanup();
|
|
989
|
+
resolve2();
|
|
990
|
+
}
|
|
991
|
+
function rejectSleep() {
|
|
992
|
+
cleanup();
|
|
993
|
+
reject(createSleepAbortError());
|
|
994
|
+
}
|
|
995
|
+
signal?.addEventListener("abort", rejectSleep, { once: true });
|
|
996
|
+
});
|
|
997
|
+
}
|
|
998
|
+
return {
|
|
999
|
+
random,
|
|
1000
|
+
now,
|
|
1001
|
+
sleep,
|
|
1002
|
+
uuid: hasDeterministicRandom ? () => createRandomUuid(random) : () => crypto.randomUUID()
|
|
1003
|
+
};
|
|
1004
|
+
}
|
|
1005
|
+
function createRandomUuid(random) {
|
|
1006
|
+
const bytes = Array.from({ length: 16 }, () => Math.floor(readRandomUnit(random()) * 256));
|
|
1007
|
+
bytes[6] = bytes[6] & 15 | 64;
|
|
1008
|
+
bytes[8] = bytes[8] & 63 | 128;
|
|
1009
|
+
const hex = bytes.map((byte) => byte.toString(16).padStart(2, "0")).join("");
|
|
1010
|
+
return [
|
|
1011
|
+
hex.slice(0, 8),
|
|
1012
|
+
hex.slice(8, 12),
|
|
1013
|
+
hex.slice(12, 16),
|
|
1014
|
+
hex.slice(16, 20),
|
|
1015
|
+
hex.slice(20)
|
|
1016
|
+
].join("-");
|
|
1017
|
+
}
|
|
1018
|
+
function readRandomUnit(value) {
|
|
1019
|
+
if (Number.isFinite(value) && value >= 0 && value < 1) {
|
|
1020
|
+
return value;
|
|
1021
|
+
}
|
|
1022
|
+
throw new RangeError("time.uuid() random source must return a number in [0, 1).");
|
|
1023
|
+
}
|
|
1024
|
+
function assertSleepDelay(ms) {
|
|
1025
|
+
if (Number.isFinite(ms) && ms >= 0) {
|
|
1026
|
+
return;
|
|
1027
|
+
}
|
|
1028
|
+
throw new RangeError("time.sleep(ms) requires a non-negative finite millisecond delay.");
|
|
1029
|
+
}
|
|
1030
|
+
function createSleepAbortError() {
|
|
1031
|
+
return new Error("time.sleep aborted.");
|
|
1032
|
+
}
|
|
1033
|
+
export {
|
|
1034
|
+
AgentSpawnError,
|
|
1035
|
+
Budget,
|
|
1036
|
+
FileSnapshotBackend,
|
|
1037
|
+
HostCallResumabilityError,
|
|
1038
|
+
HostOperationResumePolicyError,
|
|
1039
|
+
SandboxError,
|
|
1040
|
+
SnapshotValidationError,
|
|
1041
|
+
createReplayableRandom,
|
|
1042
|
+
createSpawnUsageAccumulator,
|
|
1043
|
+
declareHostOperation,
|
|
1044
|
+
deepCopyFromSandbox,
|
|
1045
|
+
deepCopyToSandbox,
|
|
1046
|
+
dump,
|
|
1047
|
+
extractBlock,
|
|
1048
|
+
findExportedConstInitializer,
|
|
1049
|
+
formatInterpreterError,
|
|
1050
|
+
lint,
|
|
1051
|
+
makeAgentModule,
|
|
1052
|
+
makeEnvModule,
|
|
1053
|
+
makeFailModule,
|
|
1054
|
+
makeFsModule,
|
|
1055
|
+
makeGitModule,
|
|
1056
|
+
makeHarnessModule,
|
|
1057
|
+
makeLogModule,
|
|
1058
|
+
makeMcpModule,
|
|
1059
|
+
makeMetricModule,
|
|
1060
|
+
makeTimeModule,
|
|
1061
|
+
noopOtelSink,
|
|
1062
|
+
parse,
|
|
1063
|
+
parseModule,
|
|
1064
|
+
registerPendingHostCallPolicy,
|
|
1065
|
+
restore,
|
|
1066
|
+
run,
|
|
1067
|
+
runHarness,
|
|
1068
|
+
runHarnessPair,
|
|
1069
|
+
runWithSpawnUsageAccumulator,
|
|
1070
|
+
splitFrontmatter
|
|
1071
|
+
};
|
|
1072
|
+
//# sourceMappingURL=index.js.map
|