@theokit/sdk 2.28.0 → 2.30.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/CHANGELOG.md +34 -0
- package/dist/a2a/index.cjs +373 -17
- package/dist/a2a/index.cjs.map +1 -1
- package/dist/a2a/index.js +373 -17
- package/dist/a2a/index.js.map +1 -1
- package/dist/{cron-BR1NCSk1.d.cts → cron-BNHJywtl.d.ts} +482 -18
- package/dist/{cron-DgEQCJ2i.d.ts → cron-t4oKI2Is.d.cts} +482 -18
- package/dist/cron.cjs +1143 -692
- package/dist/cron.cjs.map +1 -1
- package/dist/cron.d.cts +2 -2
- package/dist/cron.d.ts +2 -2
- package/dist/cron.js +1146 -695
- package/dist/cron.js.map +1 -1
- package/dist/{errors-DLMNb4Ka.d.cts → errors-DZpCGlYv.d.cts} +1 -1
- package/dist/{errors-CbY3pxY7.d.ts → errors-D_Bfo30u.d.ts} +1 -1
- package/dist/errors.d.cts +2 -2
- package/dist/eval.cjs +914 -508
- package/dist/eval.cjs.map +1 -1
- package/dist/eval.js +915 -509
- package/dist/eval.js.map +1 -1
- package/dist/index.cjs +1038 -590
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +13 -135
- package/dist/index.d.ts +13 -135
- package/dist/index.js +1037 -588
- package/dist/index.js.map +1 -1
- package/dist/internal/persistence/conversation-storage-fs.d.cts +4 -0
- package/dist/internal/persistence/conversation-storage-fs.d.ts +4 -0
- package/dist/internal/persistence/conversation-storage-memory.d.cts +4 -0
- package/dist/internal/persistence/conversation-storage-memory.d.ts +4 -0
- package/dist/internal/persistence/objective-coerce.d.cts +9 -0
- package/dist/internal/persistence/objective-coerce.d.ts +9 -0
- package/dist/internal/runtime/lifecycle/wrap-completion-check-run.d.ts +30 -0
- package/dist/internal/runtime/local-agent/local-agent-goal-extensions.d.ts +80 -0
- package/dist/internal/runtime/objective/objective-store.d.ts +33 -0
- package/dist/{run-CdWiihyU.d.cts → run-CLXKMRgq.d.cts} +71 -2
- package/dist/{run-CdWiihyU.d.ts → run-CLXKMRgq.d.ts} +71 -2
- package/dist/types/agent.d.ts +32 -1
- package/dist/types/conversation-storage.d.ts +23 -0
- package/dist/types/cron.d.ts +30 -13
- package/dist/types/goal-events.d.ts +7 -0
- package/dist/types/index.d.ts +1 -0
- package/dist/types/objective.d.ts +45 -0
- package/dist/types/run-events.d.ts +12 -1
- package/dist/types/run.d.ts +58 -0
- package/package.json +3 -3
package/dist/eval.js
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import { z, toJSONSchema } from 'zod';
|
|
2
2
|
import { createRequire } from 'module';
|
|
3
|
-
import { randomUUID,
|
|
4
|
-
import { mkdir, writeFile, readFile, unlink, open, rename, statfs, stat, rm, readdir, appendFile, access } from 'fs/promises';
|
|
5
|
-
import { join, dirname, resolve, sep, relative, isAbsolute } from 'path';
|
|
3
|
+
import { randomUUID, createHash, randomBytes } from 'crypto';
|
|
6
4
|
import { readFileSync, mkdirSync, appendFileSync, readdirSync, existsSync, realpathSync, lstatSync, readlinkSync } from 'fs';
|
|
5
|
+
import { join, dirname, resolve, sep, relative, isAbsolute } from 'path';
|
|
6
|
+
import { mkdir, writeFile, rm, readdir, readFile, unlink, open, rename, statfs, stat, access, appendFile } from 'fs/promises';
|
|
7
7
|
import { AsyncLocalStorage } from 'async_hooks';
|
|
8
8
|
import { homedir } from 'os';
|
|
9
9
|
import { execFile, spawn } from 'child_process';
|
|
@@ -779,6 +779,155 @@ var init_cwd_mutex = __esm({
|
|
|
779
779
|
tails = /* @__PURE__ */ new Map();
|
|
780
780
|
}
|
|
781
781
|
});
|
|
782
|
+
function safePathJoin(base, ...parts) {
|
|
783
|
+
if (base === "") {
|
|
784
|
+
throw new Error("safePathJoin: base must be non-empty");
|
|
785
|
+
}
|
|
786
|
+
rejectNulAndControlChars(base, "base");
|
|
787
|
+
for (const part of parts) {
|
|
788
|
+
rejectNulAndControlChars(part, "path segment");
|
|
789
|
+
}
|
|
790
|
+
const baseResolved = resolve(base);
|
|
791
|
+
const target = resolve(base, ...parts);
|
|
792
|
+
if (target !== baseResolved && !target.startsWith(baseResolved + sep)) {
|
|
793
|
+
throw new PathTraversalError(parts.join("/"), target);
|
|
794
|
+
}
|
|
795
|
+
return target;
|
|
796
|
+
}
|
|
797
|
+
function rejectNulAndControlChars(input, role) {
|
|
798
|
+
for (let i = 0; i < input.length; i++) {
|
|
799
|
+
const code = input.charCodeAt(i);
|
|
800
|
+
if (code === 0 || code >= 1 && code <= 31 || code === 127) {
|
|
801
|
+
const label = code === 0 ? "<nul-byte>" : `<control-char-0x${code.toString(16)}>`;
|
|
802
|
+
throw new PathTraversalError(`${role}: ${input}`, label);
|
|
803
|
+
}
|
|
804
|
+
}
|
|
805
|
+
}
|
|
806
|
+
function assertNoSymlinkEscape(path, base) {
|
|
807
|
+
rejectNulAndControlChars(path, "path");
|
|
808
|
+
rejectNulAndControlChars(base, "base");
|
|
809
|
+
let baseResolved;
|
|
810
|
+
try {
|
|
811
|
+
baseResolved = realpathSync(base);
|
|
812
|
+
} catch {
|
|
813
|
+
baseResolved = resolve(base);
|
|
814
|
+
}
|
|
815
|
+
const resolved = realpathOfDeepestExisting(path);
|
|
816
|
+
if (resolved === void 0) return;
|
|
817
|
+
if (resolved !== baseResolved && !resolved.startsWith(baseResolved + sep)) {
|
|
818
|
+
throw new PathTraversalError(`symlink ${path}`, resolved);
|
|
819
|
+
}
|
|
820
|
+
}
|
|
821
|
+
function realpathOfDeepestExisting(path) {
|
|
822
|
+
try {
|
|
823
|
+
return realpathSync(path);
|
|
824
|
+
} catch {
|
|
825
|
+
}
|
|
826
|
+
try {
|
|
827
|
+
const stat6 = lstatSync(path);
|
|
828
|
+
if (stat6.isSymbolicLink()) {
|
|
829
|
+
const target = readlinkSync(path);
|
|
830
|
+
const parentReal = realpathOfDeepestExisting(dirname(path));
|
|
831
|
+
const parentBase = parentReal ?? dirname(path);
|
|
832
|
+
return resolve(parentBase, target);
|
|
833
|
+
}
|
|
834
|
+
} catch {
|
|
835
|
+
}
|
|
836
|
+
let cursor = dirname(path);
|
|
837
|
+
let suffix = path.slice(cursor.length);
|
|
838
|
+
while (cursor !== dirname(cursor)) {
|
|
839
|
+
try {
|
|
840
|
+
const real = realpathSync(cursor);
|
|
841
|
+
return resolve(real, `.${suffix}`);
|
|
842
|
+
} catch {
|
|
843
|
+
suffix = path.slice(dirname(cursor).length);
|
|
844
|
+
cursor = dirname(cursor);
|
|
845
|
+
}
|
|
846
|
+
}
|
|
847
|
+
return void 0;
|
|
848
|
+
}
|
|
849
|
+
function validateArtifactPath(input) {
|
|
850
|
+
rejectKnownPrefixVectors(input);
|
|
851
|
+
const normalized = decodeAndNormalize(input);
|
|
852
|
+
rejectParentTraversal(input, normalized);
|
|
853
|
+
}
|
|
854
|
+
function rejectKnownPrefixVectors(input) {
|
|
855
|
+
if (input.includes("\0")) {
|
|
856
|
+
throw new PathTraversalError(input, "<nul-byte>");
|
|
857
|
+
}
|
|
858
|
+
if (input.startsWith("/") || input.startsWith("~")) {
|
|
859
|
+
throw new PathTraversalError(input, input);
|
|
860
|
+
}
|
|
861
|
+
if (/^[A-Za-z]:[\\/]?/.test(input)) {
|
|
862
|
+
throw new PathTraversalError(input, input);
|
|
863
|
+
}
|
|
864
|
+
}
|
|
865
|
+
function decodeAndNormalize(input) {
|
|
866
|
+
let decoded = input;
|
|
867
|
+
for (let i = 0; i < 2; i += 1) {
|
|
868
|
+
try {
|
|
869
|
+
const next = decodeURIComponent(decoded);
|
|
870
|
+
if (next === decoded) break;
|
|
871
|
+
decoded = next;
|
|
872
|
+
} catch {
|
|
873
|
+
throw new PathTraversalError(input, "<malformed-url-encoding>");
|
|
874
|
+
}
|
|
875
|
+
}
|
|
876
|
+
return decoded.replace(/\\/g, "/");
|
|
877
|
+
}
|
|
878
|
+
function rejectParentTraversal(input, normalized) {
|
|
879
|
+
for (const segment of normalized.split("/")) {
|
|
880
|
+
if (segment === ".." || segment === "..%00") {
|
|
881
|
+
throw new PathTraversalError(input, normalized);
|
|
882
|
+
}
|
|
883
|
+
}
|
|
884
|
+
if (normalized.includes("..")) {
|
|
885
|
+
throw new PathTraversalError(input, normalized);
|
|
886
|
+
}
|
|
887
|
+
}
|
|
888
|
+
function sanitizeIdentifier(input, options) {
|
|
889
|
+
const maxLen = options?.maxLen ?? 64;
|
|
890
|
+
if (input.length === 0 || input.length > maxLen) {
|
|
891
|
+
throw new ConfigurationError(`Identifier length out of range (1-${maxLen}): "${input}"`, {
|
|
892
|
+
code: "invalid_identifier"
|
|
893
|
+
});
|
|
894
|
+
}
|
|
895
|
+
rejectNulAndControlChars(input, "identifier");
|
|
896
|
+
if (!IDENTIFIER_PATTERN.test(input)) {
|
|
897
|
+
throw new ConfigurationError(`Identifier contains invalid characters: "${input}"`, {
|
|
898
|
+
code: "invalid_identifier"
|
|
899
|
+
});
|
|
900
|
+
}
|
|
901
|
+
return input.toLowerCase();
|
|
902
|
+
}
|
|
903
|
+
function safeFilenameForId(id, options) {
|
|
904
|
+
if (id.length === 0) {
|
|
905
|
+
throw new ConfigurationError("Filename id must be a non-empty string", {
|
|
906
|
+
code: "invalid_filename_id"
|
|
907
|
+
});
|
|
908
|
+
}
|
|
909
|
+
const maxLen = options?.maxLen ?? 128;
|
|
910
|
+
const lower = id.toLowerCase();
|
|
911
|
+
if (lower.length <= maxLen && IDENTIFIER_PATTERN.test(lower)) {
|
|
912
|
+
return lower;
|
|
913
|
+
}
|
|
914
|
+
return `h-${createHash("sha256").update(id).digest("hex").slice(0, 16)}`;
|
|
915
|
+
}
|
|
916
|
+
var PathTraversalError, IDENTIFIER_PATTERN;
|
|
917
|
+
var init_path_guard = __esm({
|
|
918
|
+
"src/internal/security/path-guard.ts"() {
|
|
919
|
+
init_errors();
|
|
920
|
+
PathTraversalError = class extends ConfigurationError {
|
|
921
|
+
name = "PathTraversalError";
|
|
922
|
+
constructor(input, resolvedPath) {
|
|
923
|
+
super(`Path traversal attempt: ${input} \u2192 ${resolvedPath}`, {
|
|
924
|
+
code: "path_traversal"
|
|
925
|
+
});
|
|
926
|
+
}
|
|
927
|
+
};
|
|
928
|
+
IDENTIFIER_PATTERN = /^[a-z0-9][a-z0-9\-_]*$/i;
|
|
929
|
+
}
|
|
930
|
+
});
|
|
782
931
|
function detectNetworkFsName(typeMagic) {
|
|
783
932
|
return NETWORK_FS_MAGIC.get(typeMagic) ?? null;
|
|
784
933
|
}
|
|
@@ -843,6 +992,80 @@ var init_atomic_write = __esm({
|
|
|
843
992
|
}
|
|
844
993
|
});
|
|
845
994
|
|
|
995
|
+
// src/internal/security/index.ts
|
|
996
|
+
var init_security = __esm({
|
|
997
|
+
"src/internal/security/index.ts"() {
|
|
998
|
+
init_path_guard();
|
|
999
|
+
init_redact();
|
|
1000
|
+
}
|
|
1001
|
+
});
|
|
1002
|
+
|
|
1003
|
+
// src/internal/persistence/file-lock.ts
|
|
1004
|
+
async function getProperLockfile() {
|
|
1005
|
+
if (cached !== void 0) return cached;
|
|
1006
|
+
try {
|
|
1007
|
+
const mod = await import('proper-lockfile');
|
|
1008
|
+
if (!validateLockModule(mod)) {
|
|
1009
|
+
if (!warnedStructural) {
|
|
1010
|
+
warnedStructural = true;
|
|
1011
|
+
process.stderr.write(
|
|
1012
|
+
"[theokit-sdk] proper-lockfile: imported module does NOT expose the expected `lock`/`unlock` API surface. This may indicate a supply-chain compromise or an incompatible major version. Falling back to in-process mutex (no cross-process safety). Reinstall with: pnpm add proper-lockfile@^11\n"
|
|
1013
|
+
);
|
|
1014
|
+
}
|
|
1015
|
+
cached = null;
|
|
1016
|
+
return cached;
|
|
1017
|
+
}
|
|
1018
|
+
cached = mod;
|
|
1019
|
+
} catch {
|
|
1020
|
+
cached = null;
|
|
1021
|
+
}
|
|
1022
|
+
return cached;
|
|
1023
|
+
}
|
|
1024
|
+
function validateLockModule(mod) {
|
|
1025
|
+
if (mod === null || mod === void 0 || typeof mod !== "object") return false;
|
|
1026
|
+
const m = mod;
|
|
1027
|
+
return typeof m.lock === "function" && typeof m.unlock === "function";
|
|
1028
|
+
}
|
|
1029
|
+
async function withFileLock(path, fn, options) {
|
|
1030
|
+
const lib = await getProperLockfile();
|
|
1031
|
+
if (lib === null) {
|
|
1032
|
+
if (!warnedMissing) {
|
|
1033
|
+
warnedMissing = true;
|
|
1034
|
+
process.stderr.write(
|
|
1035
|
+
"[theokit-sdk] proper-lockfile not installed; cross-process file lock unavailable. Install with: pnpm add proper-lockfile\n"
|
|
1036
|
+
);
|
|
1037
|
+
}
|
|
1038
|
+
return withCwdMutex(`file-lock:${path}`, fn);
|
|
1039
|
+
}
|
|
1040
|
+
return withCwdMutex(`file-lock:${path}`, async () => {
|
|
1041
|
+
const release = await lib.lock(path, {
|
|
1042
|
+
// EC-1: companion lockfile, target path may not exist yet.
|
|
1043
|
+
lockfilePath: `${path}.lock`,
|
|
1044
|
+
realpath: false,
|
|
1045
|
+
stale: 3e4,
|
|
1046
|
+
retries: {
|
|
1047
|
+
retries: 5,
|
|
1048
|
+
factor: 1.5,
|
|
1049
|
+
minTimeout: 100,
|
|
1050
|
+
maxTimeout: 5e3
|
|
1051
|
+
}
|
|
1052
|
+
});
|
|
1053
|
+
try {
|
|
1054
|
+
return await fn();
|
|
1055
|
+
} finally {
|
|
1056
|
+
await release();
|
|
1057
|
+
}
|
|
1058
|
+
});
|
|
1059
|
+
}
|
|
1060
|
+
var cached, warnedMissing, warnedStructural;
|
|
1061
|
+
var init_file_lock = __esm({
|
|
1062
|
+
"src/internal/persistence/file-lock.ts"() {
|
|
1063
|
+
init_cwd_mutex();
|
|
1064
|
+
warnedMissing = false;
|
|
1065
|
+
warnedStructural = false;
|
|
1066
|
+
}
|
|
1067
|
+
});
|
|
1068
|
+
|
|
846
1069
|
// src/internal/runtime/context/yaml-frontmatter.ts
|
|
847
1070
|
function parseSimpleYaml(text) {
|
|
848
1071
|
const fields = {};
|
|
@@ -1034,21 +1257,386 @@ function buildConfigFromMarkdown(entities) {
|
|
|
1034
1257
|
command: entity.frontmatter.command,
|
|
1035
1258
|
matcher: entity.frontmatter.matcher
|
|
1036
1259
|
};
|
|
1037
|
-
if (entity.frontmatter.timeoutMs !== void 0) {
|
|
1038
|
-
command.timeoutMs = entity.frontmatter.timeoutMs;
|
|
1039
|
-
}
|
|
1040
|
-
list.push(command);
|
|
1041
|
-
grouped[event] = list;
|
|
1042
|
-
}
|
|
1043
|
-
return { hooks: grouped };
|
|
1044
|
-
}
|
|
1045
|
-
var warned;
|
|
1046
|
-
var init_hooks_source = __esm({
|
|
1047
|
-
"src/internal/runtime/hooks/hooks-source.ts"() {
|
|
1048
|
-
init_errors();
|
|
1049
|
-
init_markdown_config_loader();
|
|
1050
|
-
init_hooks_frontmatter();
|
|
1051
|
-
warned = /* @__PURE__ */ new Set();
|
|
1260
|
+
if (entity.frontmatter.timeoutMs !== void 0) {
|
|
1261
|
+
command.timeoutMs = entity.frontmatter.timeoutMs;
|
|
1262
|
+
}
|
|
1263
|
+
list.push(command);
|
|
1264
|
+
grouped[event] = list;
|
|
1265
|
+
}
|
|
1266
|
+
return { hooks: grouped };
|
|
1267
|
+
}
|
|
1268
|
+
var warned;
|
|
1269
|
+
var init_hooks_source = __esm({
|
|
1270
|
+
"src/internal/runtime/hooks/hooks-source.ts"() {
|
|
1271
|
+
init_errors();
|
|
1272
|
+
init_markdown_config_loader();
|
|
1273
|
+
init_hooks_frontmatter();
|
|
1274
|
+
warned = /* @__PURE__ */ new Set();
|
|
1275
|
+
}
|
|
1276
|
+
});
|
|
1277
|
+
function sessionFilePath(cwd, agentId) {
|
|
1278
|
+
const safe3 = sanitizeIdentifier(agentId, { maxLen: 128 });
|
|
1279
|
+
return safePathJoin(cwd, ".theokit", "agents", safe3, "messages.jsonl");
|
|
1280
|
+
}
|
|
1281
|
+
async function readJsonlLines(cwd, agentId) {
|
|
1282
|
+
const path = sessionFilePath(cwd, agentId);
|
|
1283
|
+
try {
|
|
1284
|
+
const raw = await readFile(path, "utf8");
|
|
1285
|
+
return raw.split("\n").filter((line) => line.length > 0);
|
|
1286
|
+
} catch {
|
|
1287
|
+
return [];
|
|
1288
|
+
}
|
|
1289
|
+
}
|
|
1290
|
+
function warnMalformed(agentId, line) {
|
|
1291
|
+
process.stderr.write(
|
|
1292
|
+
`[theokit-sdk] skipping malformed line in messages.jsonl (${agentId}): ${line.slice(0, 80)}...
|
|
1293
|
+
`
|
|
1294
|
+
);
|
|
1295
|
+
}
|
|
1296
|
+
function hydrateSessionLine(parsed) {
|
|
1297
|
+
if (typeof parsed.text !== "string" || parsed.role === void 0) return void 0;
|
|
1298
|
+
if (parsed.role === "user" || parsed.role === "assistant") {
|
|
1299
|
+
return { role: parsed.role, text: parsed.text };
|
|
1300
|
+
}
|
|
1301
|
+
if (parsed.role === "tool_call" || parsed.role === "tool_result") {
|
|
1302
|
+
const label = parsed.role === "tool_call" ? "tool call" : "tool result";
|
|
1303
|
+
return { role: "assistant", text: `[${label}] ${parsed.text}` };
|
|
1304
|
+
}
|
|
1305
|
+
return void 0;
|
|
1306
|
+
}
|
|
1307
|
+
async function readSessionFile(cwd, agentId) {
|
|
1308
|
+
const lines = await readJsonlLines(cwd, agentId);
|
|
1309
|
+
const messages = [];
|
|
1310
|
+
for (const line of lines) {
|
|
1311
|
+
try {
|
|
1312
|
+
const msg = hydrateSessionLine(JSON.parse(line));
|
|
1313
|
+
if (msg !== void 0) messages.push(msg);
|
|
1314
|
+
} catch {
|
|
1315
|
+
warnMalformed(agentId, line);
|
|
1316
|
+
}
|
|
1317
|
+
}
|
|
1318
|
+
return messages;
|
|
1319
|
+
}
|
|
1320
|
+
async function readAllPersistedMessages(cwd, agentId) {
|
|
1321
|
+
const lines = await readJsonlLines(cwd, agentId);
|
|
1322
|
+
const messages = [];
|
|
1323
|
+
for (const line of lines) {
|
|
1324
|
+
try {
|
|
1325
|
+
const parsed = JSON.parse(line);
|
|
1326
|
+
if (parsed.role !== void 0 && VALID_ROLES.has(parsed.role) && typeof parsed.text === "string") {
|
|
1327
|
+
messages.push({
|
|
1328
|
+
role: parsed.role,
|
|
1329
|
+
text: parsed.text,
|
|
1330
|
+
at: typeof parsed.at === "number" ? parsed.at : Date.now()
|
|
1331
|
+
});
|
|
1332
|
+
}
|
|
1333
|
+
} catch {
|
|
1334
|
+
warnMalformed(agentId, line);
|
|
1335
|
+
}
|
|
1336
|
+
}
|
|
1337
|
+
return messages;
|
|
1338
|
+
}
|
|
1339
|
+
async function appendAnyPersistedMessage(cwd, agentId, record) {
|
|
1340
|
+
await appendPersistedMessages(cwd, agentId, [record]);
|
|
1341
|
+
}
|
|
1342
|
+
async function appendPersistedMessages(cwd, agentId, records) {
|
|
1343
|
+
if (records.length === 0) return;
|
|
1344
|
+
const path = sessionFilePath(cwd, agentId);
|
|
1345
|
+
const payload = records.map((r) => `${redactSecrets(JSON.stringify(r))}
|
|
1346
|
+
`).join("");
|
|
1347
|
+
const dir = dirname(path);
|
|
1348
|
+
let written = false;
|
|
1349
|
+
const attempt = async () => {
|
|
1350
|
+
await mkdir(dir, { recursive: true });
|
|
1351
|
+
await withFileLock(path, async () => {
|
|
1352
|
+
await appendFile(path, payload, "utf8");
|
|
1353
|
+
written = true;
|
|
1354
|
+
});
|
|
1355
|
+
};
|
|
1356
|
+
try {
|
|
1357
|
+
await attempt();
|
|
1358
|
+
} catch (cause) {
|
|
1359
|
+
if (written || cause.code !== "ENOENT") throw cause;
|
|
1360
|
+
await attempt();
|
|
1361
|
+
}
|
|
1362
|
+
}
|
|
1363
|
+
async function rewriteLockedSession(path, transform) {
|
|
1364
|
+
await withFileLock(path, async () => {
|
|
1365
|
+
let raw;
|
|
1366
|
+
try {
|
|
1367
|
+
raw = await readFile(path, "utf8");
|
|
1368
|
+
} catch {
|
|
1369
|
+
return;
|
|
1370
|
+
}
|
|
1371
|
+
const lines = raw.split("\n").filter((line) => line.length > 0);
|
|
1372
|
+
const next = transform(lines);
|
|
1373
|
+
if (next === void 0) return;
|
|
1374
|
+
await replaceFileAtomic(path, next);
|
|
1375
|
+
});
|
|
1376
|
+
}
|
|
1377
|
+
async function compactSessionFile(cwd, agentId, maxTurns) {
|
|
1378
|
+
const path = sessionFilePath(cwd, agentId);
|
|
1379
|
+
if (!existsSync(path)) return;
|
|
1380
|
+
await rewriteLockedSession(
|
|
1381
|
+
path,
|
|
1382
|
+
(lines) => lines.length <= maxTurns * 2 ? void 0 : `${lines.slice(-maxTurns).join("\n")}
|
|
1383
|
+
`
|
|
1384
|
+
);
|
|
1385
|
+
}
|
|
1386
|
+
async function truncateSessionTo(cwd, agentId, keepCount) {
|
|
1387
|
+
const path = sessionFilePath(cwd, agentId);
|
|
1388
|
+
if (!existsSync(path)) return 0;
|
|
1389
|
+
let kept = 0;
|
|
1390
|
+
await rewriteLockedSession(path, (lines) => {
|
|
1391
|
+
const keep = Math.max(0, Math.min(keepCount, lines.length));
|
|
1392
|
+
kept = keep;
|
|
1393
|
+
if (keep === lines.length) return void 0;
|
|
1394
|
+
return keep === 0 ? "" : `${lines.slice(0, keep).join("\n")}
|
|
1395
|
+
`;
|
|
1396
|
+
});
|
|
1397
|
+
return kept;
|
|
1398
|
+
}
|
|
1399
|
+
var VALID_ROLES;
|
|
1400
|
+
var init_agent_session_store = __esm({
|
|
1401
|
+
"src/internal/runtime/session/agent-session-store.ts"() {
|
|
1402
|
+
init_atomic_write();
|
|
1403
|
+
init_file_lock();
|
|
1404
|
+
init_security();
|
|
1405
|
+
VALID_ROLES = /* @__PURE__ */ new Set([
|
|
1406
|
+
"user",
|
|
1407
|
+
"assistant",
|
|
1408
|
+
"system",
|
|
1409
|
+
"tool_call",
|
|
1410
|
+
"tool_result"
|
|
1411
|
+
]);
|
|
1412
|
+
}
|
|
1413
|
+
});
|
|
1414
|
+
|
|
1415
|
+
// src/internal/persistence/objective-coerce.ts
|
|
1416
|
+
function coerceOptions(raw) {
|
|
1417
|
+
if (typeof raw !== "object" || raw === null) return void 0;
|
|
1418
|
+
const o = raw;
|
|
1419
|
+
const out = {};
|
|
1420
|
+
if (typeof o.maxRuns === "number") out.maxRuns = o.maxRuns;
|
|
1421
|
+
if (typeof o.judgeModel === "string") out.judgeModel = o.judgeModel;
|
|
1422
|
+
if (typeof o.prompt === "string") out.prompt = o.prompt;
|
|
1423
|
+
return Object.keys(out).length > 0 ? out : void 0;
|
|
1424
|
+
}
|
|
1425
|
+
function coerceObjectiveRecord(raw) {
|
|
1426
|
+
if (typeof raw !== "object" || raw === null) return void 0;
|
|
1427
|
+
const o = raw;
|
|
1428
|
+
if (o._schemaVersion !== 1) return void 0;
|
|
1429
|
+
if (typeof o.objective !== "string") return void 0;
|
|
1430
|
+
if (typeof o.runsUsed !== "number") return void 0;
|
|
1431
|
+
if (typeof o.status !== "string" || !STATUSES.includes(o.status))
|
|
1432
|
+
return void 0;
|
|
1433
|
+
const options = coerceOptions(o.options);
|
|
1434
|
+
return {
|
|
1435
|
+
_schemaVersion: 1,
|
|
1436
|
+
objective: o.objective,
|
|
1437
|
+
...options !== void 0 ? { options } : {},
|
|
1438
|
+
status: o.status,
|
|
1439
|
+
runsUsed: o.runsUsed
|
|
1440
|
+
};
|
|
1441
|
+
}
|
|
1442
|
+
var STATUSES;
|
|
1443
|
+
var init_objective_coerce = __esm({
|
|
1444
|
+
"src/internal/persistence/objective-coerce.ts"() {
|
|
1445
|
+
STATUSES = ["active", "done", "paused"];
|
|
1446
|
+
}
|
|
1447
|
+
});
|
|
1448
|
+
|
|
1449
|
+
// src/internal/persistence/pagination.ts
|
|
1450
|
+
function paginate(items, opts) {
|
|
1451
|
+
if (opts === void 0 || opts.offset === void 0 && opts.limit === void 0) return items;
|
|
1452
|
+
const start = Math.max(0, opts.offset ?? 0);
|
|
1453
|
+
const end = opts.limit === void 0 ? items.length : start + Math.max(0, opts.limit);
|
|
1454
|
+
return items.slice(start, end);
|
|
1455
|
+
}
|
|
1456
|
+
var init_pagination = __esm({
|
|
1457
|
+
"src/internal/persistence/pagination.ts"() {
|
|
1458
|
+
}
|
|
1459
|
+
});
|
|
1460
|
+
|
|
1461
|
+
// src/internal/persistence/session-meta.ts
|
|
1462
|
+
function applyMetaPatch(current, patch) {
|
|
1463
|
+
const next = {};
|
|
1464
|
+
if (current.title !== void 0) next.title = current.title;
|
|
1465
|
+
if (current.tag !== void 0) next.tag = current.tag;
|
|
1466
|
+
if (patch.title === null) delete next.title;
|
|
1467
|
+
else if (patch.title !== void 0) next.title = patch.title;
|
|
1468
|
+
if (patch.tag === null) delete next.tag;
|
|
1469
|
+
else if (patch.tag !== void 0) next.tag = patch.tag;
|
|
1470
|
+
return next;
|
|
1471
|
+
}
|
|
1472
|
+
function coerceSessionMeta(raw) {
|
|
1473
|
+
if (typeof raw !== "object" || raw === null) return void 0;
|
|
1474
|
+
const obj = raw;
|
|
1475
|
+
const meta = {};
|
|
1476
|
+
if (typeof obj.title === "string") meta.title = obj.title;
|
|
1477
|
+
if (typeof obj.tag === "string") meta.tag = obj.tag;
|
|
1478
|
+
return meta.title === void 0 && meta.tag === void 0 ? void 0 : meta;
|
|
1479
|
+
}
|
|
1480
|
+
var init_session_meta = __esm({
|
|
1481
|
+
"src/internal/persistence/session-meta.ts"() {
|
|
1482
|
+
}
|
|
1483
|
+
});
|
|
1484
|
+
function toStoredMessage(record) {
|
|
1485
|
+
return {
|
|
1486
|
+
role: record.role,
|
|
1487
|
+
content: record.text,
|
|
1488
|
+
at: record.at
|
|
1489
|
+
};
|
|
1490
|
+
}
|
|
1491
|
+
function toRecord(message) {
|
|
1492
|
+
return { role: message.role, text: message.content, at: message.at ?? Date.now() };
|
|
1493
|
+
}
|
|
1494
|
+
var FileSystemConversationStorage;
|
|
1495
|
+
var init_conversation_storage_fs = __esm({
|
|
1496
|
+
"src/internal/persistence/conversation-storage-fs.ts"() {
|
|
1497
|
+
init_agent_session_store();
|
|
1498
|
+
init_security();
|
|
1499
|
+
init_path_guard();
|
|
1500
|
+
init_file_lock();
|
|
1501
|
+
init_objective_coerce();
|
|
1502
|
+
init_pagination();
|
|
1503
|
+
init_session_meta();
|
|
1504
|
+
FileSystemConversationStorage = class {
|
|
1505
|
+
#root;
|
|
1506
|
+
constructor(opts = {}) {
|
|
1507
|
+
this.#root = opts.root ?? process.cwd();
|
|
1508
|
+
}
|
|
1509
|
+
/** Exposed for tests + diagnostics. The path is sanitized at use sites. */
|
|
1510
|
+
get root() {
|
|
1511
|
+
return this.#root;
|
|
1512
|
+
}
|
|
1513
|
+
async getMessages(conversationId, opts) {
|
|
1514
|
+
const records = await readAllPersistedMessages(this.#root, conversationId);
|
|
1515
|
+
const all = records.map(toStoredMessage);
|
|
1516
|
+
return paginate(all, opts);
|
|
1517
|
+
}
|
|
1518
|
+
async appendMessage(conversationId, message) {
|
|
1519
|
+
await appendAnyPersistedMessage(this.#root, conversationId, toRecord(message));
|
|
1520
|
+
}
|
|
1521
|
+
async appendMessages(conversationId, messages) {
|
|
1522
|
+
await appendPersistedMessages(this.#root, conversationId, messages.map(toRecord));
|
|
1523
|
+
}
|
|
1524
|
+
async truncateConversation(conversationId, keepCount) {
|
|
1525
|
+
return truncateSessionTo(this.#root, conversationId, keepCount);
|
|
1526
|
+
}
|
|
1527
|
+
async deleteConversation(conversationId) {
|
|
1528
|
+
const safe3 = sanitizeIdentifier(conversationId, { maxLen: 128 });
|
|
1529
|
+
const dirPath = safePathJoin(this.#root, ".theokit", "agents", safe3);
|
|
1530
|
+
await rm(dirPath, { recursive: true, force: true });
|
|
1531
|
+
}
|
|
1532
|
+
async deleteScope(prefix) {
|
|
1533
|
+
const ids = await this.listConversationIds();
|
|
1534
|
+
const matching = ids.filter((id) => id.startsWith(prefix));
|
|
1535
|
+
for (const id of matching) await this.deleteConversation(id);
|
|
1536
|
+
return matching.length;
|
|
1537
|
+
}
|
|
1538
|
+
async listConversationIds(opts = {}) {
|
|
1539
|
+
const agentsRoot = safePathJoin(this.#root, ".theokit", "agents");
|
|
1540
|
+
let entries;
|
|
1541
|
+
try {
|
|
1542
|
+
entries = await readdir(agentsRoot);
|
|
1543
|
+
} catch (cause) {
|
|
1544
|
+
if (cause.code === "ENOENT") return [];
|
|
1545
|
+
throw cause;
|
|
1546
|
+
}
|
|
1547
|
+
if (opts.limit !== void 0) return entries.slice(0, opts.limit);
|
|
1548
|
+
return entries;
|
|
1549
|
+
}
|
|
1550
|
+
async compact(conversationId, maxTurns) {
|
|
1551
|
+
await compactSessionFile(this.#root, conversationId, maxTurns);
|
|
1552
|
+
}
|
|
1553
|
+
// SE4 — session metadata persisted as a per-conversation sidecar
|
|
1554
|
+
// `<root>/.theokit/agents/<safeId>/session.json` (same sanitized perimeter as
|
|
1555
|
+
// the transcript). Kept separate from messages.jsonl so a title/tag write does
|
|
1556
|
+
// not rewrite the append-only log.
|
|
1557
|
+
#metaPath(conversationId) {
|
|
1558
|
+
const safe3 = sanitizeIdentifier(conversationId, { maxLen: 128 });
|
|
1559
|
+
return safePathJoin(this.#root, ".theokit", "agents", safe3, "session.json");
|
|
1560
|
+
}
|
|
1561
|
+
async getSessionMeta(conversationId) {
|
|
1562
|
+
try {
|
|
1563
|
+
const raw = await readFile(this.#metaPath(conversationId), "utf8");
|
|
1564
|
+
return coerceSessionMeta(JSON.parse(raw));
|
|
1565
|
+
} catch (cause) {
|
|
1566
|
+
if (cause.code === "ENOENT") return void 0;
|
|
1567
|
+
throw cause;
|
|
1568
|
+
}
|
|
1569
|
+
}
|
|
1570
|
+
async setSessionMeta(conversationId, patch) {
|
|
1571
|
+
const metaPath = this.#metaPath(conversationId);
|
|
1572
|
+
await mkdir(dirname(metaPath), { recursive: true });
|
|
1573
|
+
await withFileLock(metaPath, async () => {
|
|
1574
|
+
let current = {};
|
|
1575
|
+
try {
|
|
1576
|
+
current = coerceSessionMeta(JSON.parse(await readFile(metaPath, "utf8"))) ?? {};
|
|
1577
|
+
} catch (cause) {
|
|
1578
|
+
if (cause.code !== "ENOENT") throw cause;
|
|
1579
|
+
}
|
|
1580
|
+
const next = applyMetaPatch(current, patch);
|
|
1581
|
+
await writeFile(metaPath, redactSecrets(JSON.stringify(next)), "utf8");
|
|
1582
|
+
});
|
|
1583
|
+
}
|
|
1584
|
+
// SE33 — the durable objective is kept in `objective.json` beside the
|
|
1585
|
+
// transcript (separate from messages.jsonl + session.json). Uses the TOTAL
|
|
1586
|
+
// `safeFilenameForId` (not `sanitizeIdentifier`) so a caller-supplied
|
|
1587
|
+
// `threadId` with exotic characters (e.g. "user@example.com") hashes to a
|
|
1588
|
+
// deterministic dir instead of throwing — honoring the objective methods'
|
|
1589
|
+
// never-throw contract (ADR D6). Conforming ids pass through unchanged, so
|
|
1590
|
+
// the objective still sits beside the transcript for the normal case.
|
|
1591
|
+
#objectivePath(conversationId) {
|
|
1592
|
+
const safe3 = safeFilenameForId(conversationId, { maxLen: 128 });
|
|
1593
|
+
return safePathJoin(this.#root, ".theokit", "agents", safe3, "objective.json");
|
|
1594
|
+
}
|
|
1595
|
+
async getObjectiveRecord(conversationId) {
|
|
1596
|
+
try {
|
|
1597
|
+
const raw = await readFile(this.#objectivePath(conversationId), "utf8");
|
|
1598
|
+
return coerceObjectiveRecord(JSON.parse(raw));
|
|
1599
|
+
} catch (cause) {
|
|
1600
|
+
if (cause.code === "ENOENT") return void 0;
|
|
1601
|
+
throw cause;
|
|
1602
|
+
}
|
|
1603
|
+
}
|
|
1604
|
+
async setObjectiveRecord(conversationId, record) {
|
|
1605
|
+
const path = this.#objectivePath(conversationId);
|
|
1606
|
+
if (record === null) {
|
|
1607
|
+
await rm(path, { force: true });
|
|
1608
|
+
return;
|
|
1609
|
+
}
|
|
1610
|
+
await mkdir(dirname(path), { recursive: true });
|
|
1611
|
+
await withFileLock(path, async () => {
|
|
1612
|
+
await writeFile(path, redactSecrets(JSON.stringify(record)), "utf8");
|
|
1613
|
+
});
|
|
1614
|
+
}
|
|
1615
|
+
// SE33 (HIGH-1 fix) — atomic read-modify-write: the read that feeds `mutate`
|
|
1616
|
+
// happens INSIDE the same file lock as the write, so two concurrent progress
|
|
1617
|
+
// write-backs on one thread cannot both read a stale `runsUsed` and drop turns.
|
|
1618
|
+
async updateObjectiveRecord(conversationId, mutate) {
|
|
1619
|
+
const path = this.#objectivePath(conversationId);
|
|
1620
|
+
await mkdir(dirname(path), { recursive: true });
|
|
1621
|
+
await withFileLock(path, async () => {
|
|
1622
|
+
let current;
|
|
1623
|
+
try {
|
|
1624
|
+
current = coerceObjectiveRecord(JSON.parse(await readFile(path, "utf8")));
|
|
1625
|
+
} catch (cause) {
|
|
1626
|
+
if (cause.code !== "ENOENT") throw cause;
|
|
1627
|
+
}
|
|
1628
|
+
const next = mutate(current);
|
|
1629
|
+
if (next === void 0) return;
|
|
1630
|
+
if (next === null) {
|
|
1631
|
+
await rm(path, { force: true });
|
|
1632
|
+
return;
|
|
1633
|
+
}
|
|
1634
|
+
await writeFile(path, redactSecrets(JSON.stringify(next)), "utf8");
|
|
1635
|
+
});
|
|
1636
|
+
}
|
|
1637
|
+
async dispose() {
|
|
1638
|
+
}
|
|
1639
|
+
};
|
|
1052
1640
|
}
|
|
1053
1641
|
});
|
|
1054
1642
|
async function withToolWhitelist(whitelist, fn) {
|
|
@@ -1424,6 +2012,151 @@ var init_credential_pool_context = __esm({
|
|
|
1424
2012
|
}
|
|
1425
2013
|
});
|
|
1426
2014
|
|
|
2015
|
+
// src/internal/runtime/objective/objective-store.ts
|
|
2016
|
+
function canPersist(a) {
|
|
2017
|
+
return typeof a.getObjectiveRecord === "function" && typeof a.setObjectiveRecord === "function";
|
|
2018
|
+
}
|
|
2019
|
+
async function mutateObjective(adapter, conversationId, mutate) {
|
|
2020
|
+
if (typeof adapter.updateObjectiveRecord === "function") {
|
|
2021
|
+
await adapter.updateObjectiveRecord(conversationId, mutate);
|
|
2022
|
+
return;
|
|
2023
|
+
}
|
|
2024
|
+
const current = await adapter.getObjectiveRecord(conversationId);
|
|
2025
|
+
const next = mutate(current);
|
|
2026
|
+
if (next === void 0) return;
|
|
2027
|
+
await adapter.setObjectiveRecord(conversationId, next);
|
|
2028
|
+
}
|
|
2029
|
+
async function getObjective(adapter, conversationId) {
|
|
2030
|
+
if (!canPersist(adapter)) return void 0;
|
|
2031
|
+
return adapter.getObjectiveRecord(conversationId);
|
|
2032
|
+
}
|
|
2033
|
+
async function setObjective(adapter, conversationId, objective, options) {
|
|
2034
|
+
if (!canPersist(adapter)) return;
|
|
2035
|
+
const record = {
|
|
2036
|
+
_schemaVersion: 1,
|
|
2037
|
+
objective,
|
|
2038
|
+
...options !== void 0 ? { options } : {},
|
|
2039
|
+
status: "active",
|
|
2040
|
+
runsUsed: 0
|
|
2041
|
+
};
|
|
2042
|
+
await adapter.setObjectiveRecord(conversationId, record);
|
|
2043
|
+
}
|
|
2044
|
+
async function updateObjectiveOptions(adapter, conversationId, patch) {
|
|
2045
|
+
if (!canPersist(adapter)) return;
|
|
2046
|
+
await mutateObjective(
|
|
2047
|
+
adapter,
|
|
2048
|
+
conversationId,
|
|
2049
|
+
(current) => current === void 0 ? void 0 : { ...current, options: { ...current.options, ...patch } }
|
|
2050
|
+
);
|
|
2051
|
+
}
|
|
2052
|
+
async function writeObjectiveProgress(adapter, conversationId, progress) {
|
|
2053
|
+
if (!canPersist(adapter)) return;
|
|
2054
|
+
await mutateObjective(
|
|
2055
|
+
adapter,
|
|
2056
|
+
conversationId,
|
|
2057
|
+
(current) => current === void 0 ? void 0 : { ...current, runsUsed: progress.runsUsed, status: progress.status }
|
|
2058
|
+
);
|
|
2059
|
+
}
|
|
2060
|
+
async function clearObjective(adapter, conversationId) {
|
|
2061
|
+
if (!canPersist(adapter)) return;
|
|
2062
|
+
await adapter.setObjectiveRecord(conversationId, null);
|
|
2063
|
+
}
|
|
2064
|
+
var init_objective_store = __esm({
|
|
2065
|
+
"src/internal/runtime/objective/objective-store.ts"() {
|
|
2066
|
+
}
|
|
2067
|
+
});
|
|
2068
|
+
|
|
2069
|
+
// src/internal/runtime/local-agent/local-agent-goal-extensions.ts
|
|
2070
|
+
var local_agent_goal_extensions_exports = {};
|
|
2071
|
+
__export(local_agent_goal_extensions_exports, {
|
|
2072
|
+
formatObjectiveProjection: () => formatObjectiveProjection,
|
|
2073
|
+
localAgentClearObjective: () => localAgentClearObjective,
|
|
2074
|
+
localAgentGetObjective: () => localAgentGetObjective,
|
|
2075
|
+
localAgentSetObjective: () => localAgentSetObjective,
|
|
2076
|
+
localAgentUpdateObjectiveOptions: () => localAgentUpdateObjectiveOptions,
|
|
2077
|
+
persistDurableProgress: () => persistDurableProgress,
|
|
2078
|
+
resolveCurrentObjectiveText: () => resolveCurrentObjectiveText,
|
|
2079
|
+
resolveDurableRun: () => resolveDurableRun
|
|
2080
|
+
});
|
|
2081
|
+
function resolveAdapter(handle) {
|
|
2082
|
+
return typeof handle === "string" ? new FileSystemConversationStorage({ root: handle }) : handle;
|
|
2083
|
+
}
|
|
2084
|
+
function assertValidGoalOptions(options) {
|
|
2085
|
+
if (options.maxRuns !== void 0 && (!Number.isInteger(options.maxRuns) || options.maxRuns <= 0)) {
|
|
2086
|
+
throw new ConfigurationError(`maxRuns must be a positive integer, got ${options.maxRuns}`, {
|
|
2087
|
+
code: "invalid_objective_max_runs"
|
|
2088
|
+
});
|
|
2089
|
+
}
|
|
2090
|
+
}
|
|
2091
|
+
async function localAgentSetObjective(handle, objective, opts) {
|
|
2092
|
+
const { threadId, ...options } = opts;
|
|
2093
|
+
assertValidGoalOptions(options);
|
|
2094
|
+
await setObjective(
|
|
2095
|
+
resolveAdapter(handle),
|
|
2096
|
+
threadId,
|
|
2097
|
+
objective,
|
|
2098
|
+
Object.keys(options).length > 0 ? options : void 0
|
|
2099
|
+
);
|
|
2100
|
+
}
|
|
2101
|
+
async function localAgentGetObjective(handle, opts) {
|
|
2102
|
+
return getObjective(resolveAdapter(handle), opts.threadId);
|
|
2103
|
+
}
|
|
2104
|
+
async function localAgentUpdateObjectiveOptions(handle, opts) {
|
|
2105
|
+
const { threadId, ...patch } = opts;
|
|
2106
|
+
assertValidGoalOptions(patch);
|
|
2107
|
+
await updateObjectiveOptions(resolveAdapter(handle), threadId, patch);
|
|
2108
|
+
}
|
|
2109
|
+
async function localAgentClearObjective(handle, opts) {
|
|
2110
|
+
await clearObjective(resolveAdapter(handle), opts.threadId);
|
|
2111
|
+
}
|
|
2112
|
+
async function resolveCurrentObjectiveText(handle, threadId) {
|
|
2113
|
+
const record = await getObjective(resolveAdapter(handle), threadId);
|
|
2114
|
+
return record?.status === "active" ? record.objective : void 0;
|
|
2115
|
+
}
|
|
2116
|
+
function formatObjectiveProjection(objective, assembled) {
|
|
2117
|
+
const signal = `<current-objective>
|
|
2118
|
+
${objective}
|
|
2119
|
+
</current-objective>`;
|
|
2120
|
+
return assembled === void 0 || assembled.length === 0 ? signal : `${signal}
|
|
2121
|
+
|
|
2122
|
+
${assembled}`;
|
|
2123
|
+
}
|
|
2124
|
+
async function resolveDurableRun(handle, goalConfig, threadId, callerOptions) {
|
|
2125
|
+
const record = await getObjective(resolveAdapter(handle), threadId);
|
|
2126
|
+
if (record === void 0) return { kind: "none" };
|
|
2127
|
+
const judgeModel = callerOptions?.judgeModel ?? record.options?.judgeModel ?? goalConfig?.judgeModel;
|
|
2128
|
+
if (judgeModel === void 0) return { kind: "inert" };
|
|
2129
|
+
const totalBudget = record.options?.maxRuns ?? goalConfig?.maxRuns ?? DEFAULT_MAX_RUNS;
|
|
2130
|
+
const remaining = Math.max(0, totalBudget - record.runsUsed);
|
|
2131
|
+
if (remaining === 0) return { kind: "exhausted" };
|
|
2132
|
+
const perCall = callerOptions?.maxTurns;
|
|
2133
|
+
const maxTurns = perCall !== void 0 ? Math.min(perCall, remaining) : remaining;
|
|
2134
|
+
return {
|
|
2135
|
+
kind: "run",
|
|
2136
|
+
goal: record.objective,
|
|
2137
|
+
options: { ...callerOptions, maxTurns, judgeModel }
|
|
2138
|
+
};
|
|
2139
|
+
}
|
|
2140
|
+
async function persistDurableProgress(handle, threadId, result) {
|
|
2141
|
+
const adapter = resolveAdapter(handle);
|
|
2142
|
+
const record = await getObjective(adapter, threadId);
|
|
2143
|
+
if (record === void 0) return;
|
|
2144
|
+
const status = result.status === "completed" ? "done" : result.status === "paused" ? "paused" : "active";
|
|
2145
|
+
await writeObjectiveProgress(adapter, threadId, {
|
|
2146
|
+
runsUsed: record.runsUsed + result.turnsUsed,
|
|
2147
|
+
status
|
|
2148
|
+
});
|
|
2149
|
+
}
|
|
2150
|
+
var DEFAULT_MAX_RUNS;
|
|
2151
|
+
var init_local_agent_goal_extensions = __esm({
|
|
2152
|
+
"src/internal/runtime/local-agent/local-agent-goal-extensions.ts"() {
|
|
2153
|
+
init_errors();
|
|
2154
|
+
init_conversation_storage_fs();
|
|
2155
|
+
init_objective_store();
|
|
2156
|
+
DEFAULT_MAX_RUNS = 20;
|
|
2157
|
+
}
|
|
2158
|
+
});
|
|
2159
|
+
|
|
1427
2160
|
// src/internal/personality/context.ts
|
|
1428
2161
|
var context_exports = {};
|
|
1429
2162
|
__export(context_exports, {
|
|
@@ -3210,152 +3943,7 @@ function structurableAnswerOrThrow(result, GenerateObjectError2) {
|
|
|
3210
3943
|
// src/internal/runtime/cloud/cloud-agent.ts
|
|
3211
3944
|
init_errors();
|
|
3212
3945
|
init_cwd_mutex();
|
|
3213
|
-
|
|
3214
|
-
// src/internal/security/path-guard.ts
|
|
3215
|
-
init_errors();
|
|
3216
|
-
var PathTraversalError = class extends ConfigurationError {
|
|
3217
|
-
name = "PathTraversalError";
|
|
3218
|
-
constructor(input, resolvedPath) {
|
|
3219
|
-
super(`Path traversal attempt: ${input} \u2192 ${resolvedPath}`, {
|
|
3220
|
-
code: "path_traversal"
|
|
3221
|
-
});
|
|
3222
|
-
}
|
|
3223
|
-
};
|
|
3224
|
-
function safePathJoin(base, ...parts) {
|
|
3225
|
-
if (base === "") {
|
|
3226
|
-
throw new Error("safePathJoin: base must be non-empty");
|
|
3227
|
-
}
|
|
3228
|
-
rejectNulAndControlChars(base, "base");
|
|
3229
|
-
for (const part of parts) {
|
|
3230
|
-
rejectNulAndControlChars(part, "path segment");
|
|
3231
|
-
}
|
|
3232
|
-
const baseResolved = resolve(base);
|
|
3233
|
-
const target = resolve(base, ...parts);
|
|
3234
|
-
if (target !== baseResolved && !target.startsWith(baseResolved + sep)) {
|
|
3235
|
-
throw new PathTraversalError(parts.join("/"), target);
|
|
3236
|
-
}
|
|
3237
|
-
return target;
|
|
3238
|
-
}
|
|
3239
|
-
function rejectNulAndControlChars(input, role) {
|
|
3240
|
-
for (let i = 0; i < input.length; i++) {
|
|
3241
|
-
const code = input.charCodeAt(i);
|
|
3242
|
-
if (code === 0 || code >= 1 && code <= 31 || code === 127) {
|
|
3243
|
-
const label = code === 0 ? "<nul-byte>" : `<control-char-0x${code.toString(16)}>`;
|
|
3244
|
-
throw new PathTraversalError(`${role}: ${input}`, label);
|
|
3245
|
-
}
|
|
3246
|
-
}
|
|
3247
|
-
}
|
|
3248
|
-
function assertNoSymlinkEscape(path, base) {
|
|
3249
|
-
rejectNulAndControlChars(path, "path");
|
|
3250
|
-
rejectNulAndControlChars(base, "base");
|
|
3251
|
-
let baseResolved;
|
|
3252
|
-
try {
|
|
3253
|
-
baseResolved = realpathSync(base);
|
|
3254
|
-
} catch {
|
|
3255
|
-
baseResolved = resolve(base);
|
|
3256
|
-
}
|
|
3257
|
-
const resolved = realpathOfDeepestExisting(path);
|
|
3258
|
-
if (resolved === void 0) return;
|
|
3259
|
-
if (resolved !== baseResolved && !resolved.startsWith(baseResolved + sep)) {
|
|
3260
|
-
throw new PathTraversalError(`symlink ${path}`, resolved);
|
|
3261
|
-
}
|
|
3262
|
-
}
|
|
3263
|
-
function realpathOfDeepestExisting(path) {
|
|
3264
|
-
try {
|
|
3265
|
-
return realpathSync(path);
|
|
3266
|
-
} catch {
|
|
3267
|
-
}
|
|
3268
|
-
try {
|
|
3269
|
-
const stat6 = lstatSync(path);
|
|
3270
|
-
if (stat6.isSymbolicLink()) {
|
|
3271
|
-
const target = readlinkSync(path);
|
|
3272
|
-
const parentReal = realpathOfDeepestExisting(dirname(path));
|
|
3273
|
-
const parentBase = parentReal ?? dirname(path);
|
|
3274
|
-
return resolve(parentBase, target);
|
|
3275
|
-
}
|
|
3276
|
-
} catch {
|
|
3277
|
-
}
|
|
3278
|
-
let cursor = dirname(path);
|
|
3279
|
-
let suffix = path.slice(cursor.length);
|
|
3280
|
-
while (cursor !== dirname(cursor)) {
|
|
3281
|
-
try {
|
|
3282
|
-
const real = realpathSync(cursor);
|
|
3283
|
-
return resolve(real, `.${suffix}`);
|
|
3284
|
-
} catch {
|
|
3285
|
-
suffix = path.slice(dirname(cursor).length);
|
|
3286
|
-
cursor = dirname(cursor);
|
|
3287
|
-
}
|
|
3288
|
-
}
|
|
3289
|
-
return void 0;
|
|
3290
|
-
}
|
|
3291
|
-
var IDENTIFIER_PATTERN = /^[a-z0-9][a-z0-9\-_]*$/i;
|
|
3292
|
-
function validateArtifactPath(input) {
|
|
3293
|
-
rejectKnownPrefixVectors(input);
|
|
3294
|
-
const normalized = decodeAndNormalize(input);
|
|
3295
|
-
rejectParentTraversal(input, normalized);
|
|
3296
|
-
}
|
|
3297
|
-
function rejectKnownPrefixVectors(input) {
|
|
3298
|
-
if (input.includes("\0")) {
|
|
3299
|
-
throw new PathTraversalError(input, "<nul-byte>");
|
|
3300
|
-
}
|
|
3301
|
-
if (input.startsWith("/") || input.startsWith("~")) {
|
|
3302
|
-
throw new PathTraversalError(input, input);
|
|
3303
|
-
}
|
|
3304
|
-
if (/^[A-Za-z]:[\\/]?/.test(input)) {
|
|
3305
|
-
throw new PathTraversalError(input, input);
|
|
3306
|
-
}
|
|
3307
|
-
}
|
|
3308
|
-
function decodeAndNormalize(input) {
|
|
3309
|
-
let decoded = input;
|
|
3310
|
-
for (let i = 0; i < 2; i += 1) {
|
|
3311
|
-
try {
|
|
3312
|
-
const next = decodeURIComponent(decoded);
|
|
3313
|
-
if (next === decoded) break;
|
|
3314
|
-
decoded = next;
|
|
3315
|
-
} catch {
|
|
3316
|
-
throw new PathTraversalError(input, "<malformed-url-encoding>");
|
|
3317
|
-
}
|
|
3318
|
-
}
|
|
3319
|
-
return decoded.replace(/\\/g, "/");
|
|
3320
|
-
}
|
|
3321
|
-
function rejectParentTraversal(input, normalized) {
|
|
3322
|
-
for (const segment of normalized.split("/")) {
|
|
3323
|
-
if (segment === ".." || segment === "..%00") {
|
|
3324
|
-
throw new PathTraversalError(input, normalized);
|
|
3325
|
-
}
|
|
3326
|
-
}
|
|
3327
|
-
if (normalized.includes("..")) {
|
|
3328
|
-
throw new PathTraversalError(input, normalized);
|
|
3329
|
-
}
|
|
3330
|
-
}
|
|
3331
|
-
function sanitizeIdentifier(input, options) {
|
|
3332
|
-
const maxLen = options?.maxLen ?? 64;
|
|
3333
|
-
if (input.length === 0 || input.length > maxLen) {
|
|
3334
|
-
throw new ConfigurationError(`Identifier length out of range (1-${maxLen}): "${input}"`, {
|
|
3335
|
-
code: "invalid_identifier"
|
|
3336
|
-
});
|
|
3337
|
-
}
|
|
3338
|
-
rejectNulAndControlChars(input, "identifier");
|
|
3339
|
-
if (!IDENTIFIER_PATTERN.test(input)) {
|
|
3340
|
-
throw new ConfigurationError(`Identifier contains invalid characters: "${input}"`, {
|
|
3341
|
-
code: "invalid_identifier"
|
|
3342
|
-
});
|
|
3343
|
-
}
|
|
3344
|
-
return input.toLowerCase();
|
|
3345
|
-
}
|
|
3346
|
-
function safeFilenameForId(id, options) {
|
|
3347
|
-
if (id.length === 0) {
|
|
3348
|
-
throw new ConfigurationError("Filename id must be a non-empty string", {
|
|
3349
|
-
code: "invalid_filename_id"
|
|
3350
|
-
});
|
|
3351
|
-
}
|
|
3352
|
-
const maxLen = options?.maxLen;
|
|
3353
|
-
const lower = id.toLowerCase();
|
|
3354
|
-
if (lower.length <= maxLen && IDENTIFIER_PATTERN.test(lower)) {
|
|
3355
|
-
return lower;
|
|
3356
|
-
}
|
|
3357
|
-
return `h-${createHash("sha256").update(id).digest("hex").slice(0, 16)}`;
|
|
3358
|
-
}
|
|
3946
|
+
init_path_guard();
|
|
3359
3947
|
|
|
3360
3948
|
// src/internal/runtime/model-selection.ts
|
|
3361
3949
|
init_errors();
|
|
@@ -3845,8 +4433,8 @@ function serializeMemory2(memory) {
|
|
|
3845
4433
|
return result;
|
|
3846
4434
|
}
|
|
3847
4435
|
|
|
3848
|
-
// src/internal/
|
|
3849
|
-
|
|
4436
|
+
// src/internal/runtime/fixtures/fixture-responder.ts
|
|
4437
|
+
init_security();
|
|
3850
4438
|
|
|
3851
4439
|
// src/internal/runtime/fixtures/fixture-events.ts
|
|
3852
4440
|
function assistantOnlyConversation(text) {
|
|
@@ -3965,6 +4553,10 @@ function sanitizeMcpName(name) {
|
|
|
3965
4553
|
// src/internal/memory/storage/markdown-store.ts
|
|
3966
4554
|
init_atomic_write();
|
|
3967
4555
|
init_cwd_mutex();
|
|
4556
|
+
|
|
4557
|
+
// src/internal/memory/types.ts
|
|
4558
|
+
init_path_guard();
|
|
4559
|
+
init_security();
|
|
3968
4560
|
function legacyMemoryJsonPath(cwd, config) {
|
|
3969
4561
|
if (config.storePath !== void 0) {
|
|
3970
4562
|
return resolve(cwd, config.storePath);
|
|
@@ -5282,78 +5874,17 @@ function isStdioCommandConfig(config) {
|
|
|
5282
5874
|
if (obj.type !== void 0 && obj.type !== "stdio") return false;
|
|
5283
5875
|
return typeof obj.command === "string" && obj.command.length > 0;
|
|
5284
5876
|
}
|
|
5285
|
-
function isLocalPath(command) {
|
|
5286
|
-
return command.startsWith("/") || command.startsWith("~/") || command.startsWith("./") || command.startsWith("../");
|
|
5287
|
-
}
|
|
5288
|
-
|
|
5289
|
-
// src/internal/runtime/local-agent/local-agent.ts
|
|
5290
|
-
init_errors();
|
|
5291
|
-
init_cwd_mutex();
|
|
5292
|
-
|
|
5293
|
-
// src/internal/personality/store.ts
|
|
5294
|
-
init_atomic_write();
|
|
5295
|
-
|
|
5296
|
-
// src/internal/persistence/file-lock.ts
|
|
5297
|
-
init_cwd_mutex();
|
|
5298
|
-
var cached;
|
|
5299
|
-
var warnedMissing = false;
|
|
5300
|
-
var warnedStructural = false;
|
|
5301
|
-
async function getProperLockfile() {
|
|
5302
|
-
if (cached !== void 0) return cached;
|
|
5303
|
-
try {
|
|
5304
|
-
const mod = await import('proper-lockfile');
|
|
5305
|
-
if (!validateLockModule(mod)) {
|
|
5306
|
-
if (!warnedStructural) {
|
|
5307
|
-
warnedStructural = true;
|
|
5308
|
-
process.stderr.write(
|
|
5309
|
-
"[theokit-sdk] proper-lockfile: imported module does NOT expose the expected `lock`/`unlock` API surface. This may indicate a supply-chain compromise or an incompatible major version. Falling back to in-process mutex (no cross-process safety). Reinstall with: pnpm add proper-lockfile@^11\n"
|
|
5310
|
-
);
|
|
5311
|
-
}
|
|
5312
|
-
cached = null;
|
|
5313
|
-
return cached;
|
|
5314
|
-
}
|
|
5315
|
-
cached = mod;
|
|
5316
|
-
} catch {
|
|
5317
|
-
cached = null;
|
|
5318
|
-
}
|
|
5319
|
-
return cached;
|
|
5320
|
-
}
|
|
5321
|
-
function validateLockModule(mod) {
|
|
5322
|
-
if (mod === null || mod === void 0 || typeof mod !== "object") return false;
|
|
5323
|
-
const m = mod;
|
|
5324
|
-
return typeof m.lock === "function" && typeof m.unlock === "function";
|
|
5325
|
-
}
|
|
5326
|
-
async function withFileLock(path, fn, options) {
|
|
5327
|
-
const lib = await getProperLockfile();
|
|
5328
|
-
if (lib === null) {
|
|
5329
|
-
if (!warnedMissing) {
|
|
5330
|
-
warnedMissing = true;
|
|
5331
|
-
process.stderr.write(
|
|
5332
|
-
"[theokit-sdk] proper-lockfile not installed; cross-process file lock unavailable. Install with: pnpm add proper-lockfile\n"
|
|
5333
|
-
);
|
|
5334
|
-
}
|
|
5335
|
-
return withCwdMutex(`file-lock:${path}`, fn);
|
|
5336
|
-
}
|
|
5337
|
-
return withCwdMutex(`file-lock:${path}`, async () => {
|
|
5338
|
-
const release = await lib.lock(path, {
|
|
5339
|
-
// EC-1: companion lockfile, target path may not exist yet.
|
|
5340
|
-
lockfilePath: `${path}.lock`,
|
|
5341
|
-
realpath: false,
|
|
5342
|
-
stale: 3e4,
|
|
5343
|
-
retries: {
|
|
5344
|
-
retries: 5,
|
|
5345
|
-
factor: 1.5,
|
|
5346
|
-
minTimeout: 100,
|
|
5347
|
-
maxTimeout: 5e3
|
|
5348
|
-
}
|
|
5349
|
-
});
|
|
5350
|
-
try {
|
|
5351
|
-
return await fn();
|
|
5352
|
-
} finally {
|
|
5353
|
-
await release();
|
|
5354
|
-
}
|
|
5355
|
-
});
|
|
5356
|
-
}
|
|
5877
|
+
function isLocalPath(command) {
|
|
5878
|
+
return command.startsWith("/") || command.startsWith("~/") || command.startsWith("./") || command.startsWith("../");
|
|
5879
|
+
}
|
|
5880
|
+
|
|
5881
|
+
// src/internal/runtime/local-agent/local-agent.ts
|
|
5882
|
+
init_errors();
|
|
5883
|
+
init_cwd_mutex();
|
|
5884
|
+
|
|
5885
|
+
// src/internal/personality/store.ts
|
|
5886
|
+
init_atomic_write();
|
|
5887
|
+
init_file_lock();
|
|
5357
5888
|
var THEOKIT_DIR_NAME = ".theokit";
|
|
5358
5889
|
function getTheokitHome(cwd) {
|
|
5359
5890
|
const override = process.env.THEOKIT_HOME?.trim();
|
|
@@ -5795,6 +6326,9 @@ var HISTOGRAM_NAMES = {
|
|
|
5795
6326
|
/** M3 #66 — count of finishes where the provider omitted usage (silent undercount). */
|
|
5796
6327
|
LLM_USAGE_MISSING: "theokit_llm_usage_missing"
|
|
5797
6328
|
};
|
|
6329
|
+
|
|
6330
|
+
// src/internal/telemetry/tracer.ts
|
|
6331
|
+
init_security();
|
|
5798
6332
|
function safeRequire(moduleName) {
|
|
5799
6333
|
try {
|
|
5800
6334
|
const r = createRequire(import.meta.url);
|
|
@@ -6367,6 +6901,7 @@ function parseDecisionFromStdout(stdout) {
|
|
|
6367
6901
|
|
|
6368
6902
|
// src/internal/memory/storage/session-summary-writer.ts
|
|
6369
6903
|
init_atomic_write();
|
|
6904
|
+
init_path_guard();
|
|
6370
6905
|
var MAX_TURN_CHARS = 2e3;
|
|
6371
6906
|
function sessionsDir(cwd) {
|
|
6372
6907
|
return join(memoryDir(cwd), "sessions");
|
|
@@ -6427,262 +6962,9 @@ function resolveActiveMemorySummaryForSend(legacySummary, portPathEnabled) {
|
|
|
6427
6962
|
return legacySummary;
|
|
6428
6963
|
}
|
|
6429
6964
|
|
|
6430
|
-
// src/internal/runtime/session/agent-session-store.ts
|
|
6431
|
-
init_atomic_write();
|
|
6432
|
-
var VALID_ROLES = /* @__PURE__ */ new Set([
|
|
6433
|
-
"user",
|
|
6434
|
-
"assistant",
|
|
6435
|
-
"system",
|
|
6436
|
-
"tool_call",
|
|
6437
|
-
"tool_result"
|
|
6438
|
-
]);
|
|
6439
|
-
function sessionFilePath(cwd, agentId) {
|
|
6440
|
-
const safe3 = sanitizeIdentifier(agentId, { maxLen: 128 });
|
|
6441
|
-
return safePathJoin(cwd, ".theokit", "agents", safe3, "messages.jsonl");
|
|
6442
|
-
}
|
|
6443
|
-
async function readJsonlLines(cwd, agentId) {
|
|
6444
|
-
const path = sessionFilePath(cwd, agentId);
|
|
6445
|
-
try {
|
|
6446
|
-
const raw = await readFile(path, "utf8");
|
|
6447
|
-
return raw.split("\n").filter((line) => line.length > 0);
|
|
6448
|
-
} catch {
|
|
6449
|
-
return [];
|
|
6450
|
-
}
|
|
6451
|
-
}
|
|
6452
|
-
function warnMalformed(agentId, line) {
|
|
6453
|
-
process.stderr.write(
|
|
6454
|
-
`[theokit-sdk] skipping malformed line in messages.jsonl (${agentId}): ${line.slice(0, 80)}...
|
|
6455
|
-
`
|
|
6456
|
-
);
|
|
6457
|
-
}
|
|
6458
|
-
function hydrateSessionLine(parsed) {
|
|
6459
|
-
if (typeof parsed.text !== "string" || parsed.role === void 0) return void 0;
|
|
6460
|
-
if (parsed.role === "user" || parsed.role === "assistant") {
|
|
6461
|
-
return { role: parsed.role, text: parsed.text };
|
|
6462
|
-
}
|
|
6463
|
-
if (parsed.role === "tool_call" || parsed.role === "tool_result") {
|
|
6464
|
-
const label = parsed.role === "tool_call" ? "tool call" : "tool result";
|
|
6465
|
-
return { role: "assistant", text: `[${label}] ${parsed.text}` };
|
|
6466
|
-
}
|
|
6467
|
-
return void 0;
|
|
6468
|
-
}
|
|
6469
|
-
async function readSessionFile(cwd, agentId) {
|
|
6470
|
-
const lines = await readJsonlLines(cwd, agentId);
|
|
6471
|
-
const messages = [];
|
|
6472
|
-
for (const line of lines) {
|
|
6473
|
-
try {
|
|
6474
|
-
const msg = hydrateSessionLine(JSON.parse(line));
|
|
6475
|
-
if (msg !== void 0) messages.push(msg);
|
|
6476
|
-
} catch {
|
|
6477
|
-
warnMalformed(agentId, line);
|
|
6478
|
-
}
|
|
6479
|
-
}
|
|
6480
|
-
return messages;
|
|
6481
|
-
}
|
|
6482
|
-
async function readAllPersistedMessages(cwd, agentId) {
|
|
6483
|
-
const lines = await readJsonlLines(cwd, agentId);
|
|
6484
|
-
const messages = [];
|
|
6485
|
-
for (const line of lines) {
|
|
6486
|
-
try {
|
|
6487
|
-
const parsed = JSON.parse(line);
|
|
6488
|
-
if (parsed.role !== void 0 && VALID_ROLES.has(parsed.role) && typeof parsed.text === "string") {
|
|
6489
|
-
messages.push({
|
|
6490
|
-
role: parsed.role,
|
|
6491
|
-
text: parsed.text,
|
|
6492
|
-
at: typeof parsed.at === "number" ? parsed.at : Date.now()
|
|
6493
|
-
});
|
|
6494
|
-
}
|
|
6495
|
-
} catch {
|
|
6496
|
-
warnMalformed(agentId, line);
|
|
6497
|
-
}
|
|
6498
|
-
}
|
|
6499
|
-
return messages;
|
|
6500
|
-
}
|
|
6501
|
-
async function appendAnyPersistedMessage(cwd, agentId, record) {
|
|
6502
|
-
await appendPersistedMessages(cwd, agentId, [record]);
|
|
6503
|
-
}
|
|
6504
|
-
async function appendPersistedMessages(cwd, agentId, records) {
|
|
6505
|
-
if (records.length === 0) return;
|
|
6506
|
-
const path = sessionFilePath(cwd, agentId);
|
|
6507
|
-
const payload = records.map((r) => `${redactSecrets(JSON.stringify(r))}
|
|
6508
|
-
`).join("");
|
|
6509
|
-
const dir = dirname(path);
|
|
6510
|
-
let written = false;
|
|
6511
|
-
const attempt = async () => {
|
|
6512
|
-
await mkdir(dir, { recursive: true });
|
|
6513
|
-
await withFileLock(path, async () => {
|
|
6514
|
-
await appendFile(path, payload, "utf8");
|
|
6515
|
-
written = true;
|
|
6516
|
-
});
|
|
6517
|
-
};
|
|
6518
|
-
try {
|
|
6519
|
-
await attempt();
|
|
6520
|
-
} catch (cause) {
|
|
6521
|
-
if (written || cause.code !== "ENOENT") throw cause;
|
|
6522
|
-
await attempt();
|
|
6523
|
-
}
|
|
6524
|
-
}
|
|
6525
|
-
async function rewriteLockedSession(path, transform) {
|
|
6526
|
-
await withFileLock(path, async () => {
|
|
6527
|
-
let raw;
|
|
6528
|
-
try {
|
|
6529
|
-
raw = await readFile(path, "utf8");
|
|
6530
|
-
} catch {
|
|
6531
|
-
return;
|
|
6532
|
-
}
|
|
6533
|
-
const lines = raw.split("\n").filter((line) => line.length > 0);
|
|
6534
|
-
const next = transform(lines);
|
|
6535
|
-
if (next === void 0) return;
|
|
6536
|
-
await replaceFileAtomic(path, next);
|
|
6537
|
-
});
|
|
6538
|
-
}
|
|
6539
|
-
async function compactSessionFile(cwd, agentId, maxTurns) {
|
|
6540
|
-
const path = sessionFilePath(cwd, agentId);
|
|
6541
|
-
if (!existsSync(path)) return;
|
|
6542
|
-
await rewriteLockedSession(
|
|
6543
|
-
path,
|
|
6544
|
-
(lines) => lines.length <= maxTurns * 2 ? void 0 : `${lines.slice(-maxTurns).join("\n")}
|
|
6545
|
-
`
|
|
6546
|
-
);
|
|
6547
|
-
}
|
|
6548
|
-
async function truncateSessionTo(cwd, agentId, keepCount) {
|
|
6549
|
-
const path = sessionFilePath(cwd, agentId);
|
|
6550
|
-
if (!existsSync(path)) return 0;
|
|
6551
|
-
let kept = 0;
|
|
6552
|
-
await rewriteLockedSession(path, (lines) => {
|
|
6553
|
-
const keep = Math.max(0, Math.min(keepCount, lines.length));
|
|
6554
|
-
kept = keep;
|
|
6555
|
-
if (keep === lines.length) return void 0;
|
|
6556
|
-
return keep === 0 ? "" : `${lines.slice(0, keep).join("\n")}
|
|
6557
|
-
`;
|
|
6558
|
-
});
|
|
6559
|
-
return kept;
|
|
6560
|
-
}
|
|
6561
|
-
|
|
6562
|
-
// src/internal/persistence/pagination.ts
|
|
6563
|
-
function paginate(items, opts) {
|
|
6564
|
-
if (opts === void 0 || opts.offset === void 0 && opts.limit === void 0) return items;
|
|
6565
|
-
const start = Math.max(0, opts.offset ?? 0);
|
|
6566
|
-
const end = opts.limit === void 0 ? items.length : start + Math.max(0, opts.limit);
|
|
6567
|
-
return items.slice(start, end);
|
|
6568
|
-
}
|
|
6569
|
-
|
|
6570
|
-
// src/internal/persistence/session-meta.ts
|
|
6571
|
-
function applyMetaPatch(current, patch) {
|
|
6572
|
-
const next = {};
|
|
6573
|
-
if (current.title !== void 0) next.title = current.title;
|
|
6574
|
-
if (current.tag !== void 0) next.tag = current.tag;
|
|
6575
|
-
if (patch.title === null) delete next.title;
|
|
6576
|
-
else if (patch.title !== void 0) next.title = patch.title;
|
|
6577
|
-
if (patch.tag === null) delete next.tag;
|
|
6578
|
-
else if (patch.tag !== void 0) next.tag = patch.tag;
|
|
6579
|
-
return next;
|
|
6580
|
-
}
|
|
6581
|
-
function coerceSessionMeta(raw) {
|
|
6582
|
-
if (typeof raw !== "object" || raw === null) return void 0;
|
|
6583
|
-
const obj = raw;
|
|
6584
|
-
const meta = {};
|
|
6585
|
-
if (typeof obj.title === "string") meta.title = obj.title;
|
|
6586
|
-
if (typeof obj.tag === "string") meta.tag = obj.tag;
|
|
6587
|
-
return meta.title === void 0 && meta.tag === void 0 ? void 0 : meta;
|
|
6588
|
-
}
|
|
6589
|
-
|
|
6590
|
-
// src/internal/persistence/conversation-storage-fs.ts
|
|
6591
|
-
var FileSystemConversationStorage = class {
|
|
6592
|
-
#root;
|
|
6593
|
-
constructor(opts = {}) {
|
|
6594
|
-
this.#root = opts.root ?? process.cwd();
|
|
6595
|
-
}
|
|
6596
|
-
/** Exposed for tests + diagnostics. The path is sanitized at use sites. */
|
|
6597
|
-
get root() {
|
|
6598
|
-
return this.#root;
|
|
6599
|
-
}
|
|
6600
|
-
async getMessages(conversationId, opts) {
|
|
6601
|
-
const records = await readAllPersistedMessages(this.#root, conversationId);
|
|
6602
|
-
const all = records.map(toStoredMessage);
|
|
6603
|
-
return paginate(all, opts);
|
|
6604
|
-
}
|
|
6605
|
-
async appendMessage(conversationId, message) {
|
|
6606
|
-
await appendAnyPersistedMessage(this.#root, conversationId, toRecord(message));
|
|
6607
|
-
}
|
|
6608
|
-
async appendMessages(conversationId, messages) {
|
|
6609
|
-
await appendPersistedMessages(this.#root, conversationId, messages.map(toRecord));
|
|
6610
|
-
}
|
|
6611
|
-
async truncateConversation(conversationId, keepCount) {
|
|
6612
|
-
return truncateSessionTo(this.#root, conversationId, keepCount);
|
|
6613
|
-
}
|
|
6614
|
-
async deleteConversation(conversationId) {
|
|
6615
|
-
const safe3 = sanitizeIdentifier(conversationId, { maxLen: 128 });
|
|
6616
|
-
const dirPath = safePathJoin(this.#root, ".theokit", "agents", safe3);
|
|
6617
|
-
await rm(dirPath, { recursive: true, force: true });
|
|
6618
|
-
}
|
|
6619
|
-
async deleteScope(prefix) {
|
|
6620
|
-
const ids = await this.listConversationIds();
|
|
6621
|
-
const matching = ids.filter((id) => id.startsWith(prefix));
|
|
6622
|
-
for (const id of matching) await this.deleteConversation(id);
|
|
6623
|
-
return matching.length;
|
|
6624
|
-
}
|
|
6625
|
-
async listConversationIds(opts = {}) {
|
|
6626
|
-
const agentsRoot = safePathJoin(this.#root, ".theokit", "agents");
|
|
6627
|
-
let entries;
|
|
6628
|
-
try {
|
|
6629
|
-
entries = await readdir(agentsRoot);
|
|
6630
|
-
} catch (cause) {
|
|
6631
|
-
if (cause.code === "ENOENT") return [];
|
|
6632
|
-
throw cause;
|
|
6633
|
-
}
|
|
6634
|
-
if (opts.limit !== void 0) return entries.slice(0, opts.limit);
|
|
6635
|
-
return entries;
|
|
6636
|
-
}
|
|
6637
|
-
async compact(conversationId, maxTurns) {
|
|
6638
|
-
await compactSessionFile(this.#root, conversationId, maxTurns);
|
|
6639
|
-
}
|
|
6640
|
-
// SE4 — session metadata persisted as a per-conversation sidecar
|
|
6641
|
-
// `<root>/.theokit/agents/<safeId>/session.json` (same sanitized perimeter as
|
|
6642
|
-
// the transcript). Kept separate from messages.jsonl so a title/tag write does
|
|
6643
|
-
// not rewrite the append-only log.
|
|
6644
|
-
#metaPath(conversationId) {
|
|
6645
|
-
const safe3 = sanitizeIdentifier(conversationId, { maxLen: 128 });
|
|
6646
|
-
return safePathJoin(this.#root, ".theokit", "agents", safe3, "session.json");
|
|
6647
|
-
}
|
|
6648
|
-
async getSessionMeta(conversationId) {
|
|
6649
|
-
try {
|
|
6650
|
-
const raw = await readFile(this.#metaPath(conversationId), "utf8");
|
|
6651
|
-
return coerceSessionMeta(JSON.parse(raw));
|
|
6652
|
-
} catch (cause) {
|
|
6653
|
-
if (cause.code === "ENOENT") return void 0;
|
|
6654
|
-
throw cause;
|
|
6655
|
-
}
|
|
6656
|
-
}
|
|
6657
|
-
async setSessionMeta(conversationId, patch) {
|
|
6658
|
-
const metaPath = this.#metaPath(conversationId);
|
|
6659
|
-
await mkdir(dirname(metaPath), { recursive: true });
|
|
6660
|
-
await withFileLock(metaPath, async () => {
|
|
6661
|
-
let current = {};
|
|
6662
|
-
try {
|
|
6663
|
-
current = coerceSessionMeta(JSON.parse(await readFile(metaPath, "utf8"))) ?? {};
|
|
6664
|
-
} catch (cause) {
|
|
6665
|
-
if (cause.code !== "ENOENT") throw cause;
|
|
6666
|
-
}
|
|
6667
|
-
const next = applyMetaPatch(current, patch);
|
|
6668
|
-
await writeFile(metaPath, redactSecrets(JSON.stringify(next)), "utf8");
|
|
6669
|
-
});
|
|
6670
|
-
}
|
|
6671
|
-
async dispose() {
|
|
6672
|
-
}
|
|
6673
|
-
};
|
|
6674
|
-
function toStoredMessage(record) {
|
|
6675
|
-
return {
|
|
6676
|
-
role: record.role,
|
|
6677
|
-
content: record.text,
|
|
6678
|
-
at: record.at
|
|
6679
|
-
};
|
|
6680
|
-
}
|
|
6681
|
-
function toRecord(message) {
|
|
6682
|
-
return { role: message.role, text: message.content, at: message.at ?? Date.now() };
|
|
6683
|
-
}
|
|
6684
|
-
|
|
6685
6965
|
// src/internal/runtime/session/agent-session.ts
|
|
6966
|
+
init_conversation_storage_fs();
|
|
6967
|
+
init_agent_session_store();
|
|
6686
6968
|
var DEFAULT_MAX_TURNS = 200;
|
|
6687
6969
|
var COMPACTION_CHECK_INTERVAL = 50;
|
|
6688
6970
|
var sessions = /* @__PURE__ */ new Map();
|
|
@@ -7122,6 +7404,7 @@ function parseFrontmatterFields(frontmatter) {
|
|
|
7122
7404
|
|
|
7123
7405
|
// src/internal/runtime/skills/discover-skills.ts
|
|
7124
7406
|
init_errors();
|
|
7407
|
+
init_path_guard();
|
|
7125
7408
|
|
|
7126
7409
|
// src/internal/runtime/skills/skill-frontmatter.ts
|
|
7127
7410
|
init_errors();
|
|
@@ -8442,6 +8725,7 @@ function tokenizeContent(content) {
|
|
|
8442
8725
|
// src/internal/runtime/plugins/plugins-manager.ts
|
|
8443
8726
|
init_errors();
|
|
8444
8727
|
init_markdown_config_loader();
|
|
8728
|
+
init_path_guard();
|
|
8445
8729
|
init_hooks_source();
|
|
8446
8730
|
var PluginFrontmatterSchema = z.object({
|
|
8447
8731
|
/** Plugin identifier; defaults to folder name if omitted. */
|
|
@@ -10901,6 +11185,7 @@ function registerBuiltins() {
|
|
|
10901
11185
|
init_errors();
|
|
10902
11186
|
|
|
10903
11187
|
// src/internal/error-mappers/shared.ts
|
|
11188
|
+
init_security();
|
|
10904
11189
|
var RAW_MAX_BYTES = 2048;
|
|
10905
11190
|
function parseRetryAfter(headers) {
|
|
10906
11191
|
if (headers === void 0) return void 0;
|
|
@@ -13032,6 +13317,7 @@ function selectTransport(profile, apiKey) {
|
|
|
13032
13317
|
|
|
13033
13318
|
// src/internal/mcp/client.ts
|
|
13034
13319
|
init_errors();
|
|
13320
|
+
init_path_guard();
|
|
13035
13321
|
function createMcpClient(name, config, fetchImpl = fetch) {
|
|
13036
13322
|
if (isStdio(config)) return new StdioMcpClient(name, config);
|
|
13037
13323
|
return new HttpMcpClient(name, config, fetchImpl);
|
|
@@ -13741,6 +14027,9 @@ async function readProjectMcpServers(cwd) {
|
|
|
13741
14027
|
}
|
|
13742
14028
|
}
|
|
13743
14029
|
|
|
14030
|
+
// src/internal/runtime/local-agent/local-agent.ts
|
|
14031
|
+
init_local_agent_goal_extensions();
|
|
14032
|
+
|
|
13744
14033
|
// src/internal/runtime/local-agent/local-agent-invalidate.ts
|
|
13745
14034
|
async function applyDeferredInvalidation(agentId, pending, refresh) {
|
|
13746
14035
|
process.stderr.write(
|
|
@@ -16071,16 +16360,49 @@ async function localAgentUsePersonality(args) {
|
|
|
16071
16360
|
init_local_agent_plugins();
|
|
16072
16361
|
|
|
16073
16362
|
// src/internal/runtime/local-agent/local-agent-runtime-extensions.ts
|
|
16074
|
-
function
|
|
16363
|
+
function pausedReason(kind, threadId) {
|
|
16364
|
+
switch (kind) {
|
|
16365
|
+
case "none":
|
|
16366
|
+
return `no durable objective set for thread "${threadId}" \u2014 call setObjective() first`;
|
|
16367
|
+
case "inert":
|
|
16368
|
+
return `durable objective for thread "${threadId}" is inert \u2014 no judge resolved (set a judge to activate)`;
|
|
16369
|
+
case "exhausted":
|
|
16370
|
+
return `durable objective for thread "${threadId}" exhausted its run budget \u2014 raise maxRuns to resume`;
|
|
16371
|
+
}
|
|
16372
|
+
}
|
|
16373
|
+
function localAgentRunUntil(agent, goal, options, durable) {
|
|
16075
16374
|
async function* wrap() {
|
|
16076
16375
|
const { runUntilImpl: runUntilImpl2 } = await Promise.resolve().then(() => (init_run_until(), run_until_exports));
|
|
16077
|
-
const
|
|
16078
|
-
|
|
16079
|
-
|
|
16080
|
-
|
|
16081
|
-
|
|
16376
|
+
const buildDeps = async () => {
|
|
16377
|
+
const { judgeCallImpl: judgeCallImpl2 } = await Promise.resolve().then(() => (init_judge_call(), judge_call_exports));
|
|
16378
|
+
const { getAgentFacade: getAgentFacade2 } = await Promise.resolve().then(() => (init_agent_factory_registry(), agent_factory_registry_exports));
|
|
16379
|
+
const create = getAgentFacade2().create;
|
|
16380
|
+
return {
|
|
16381
|
+
judge: (ctx, opts) => judgeCallImpl2(ctx, opts, { create })
|
|
16382
|
+
};
|
|
16082
16383
|
};
|
|
16083
|
-
|
|
16384
|
+
if (goal !== void 0) {
|
|
16385
|
+
return yield* runUntilImpl2(agent, goal, options, await buildDeps());
|
|
16386
|
+
}
|
|
16387
|
+
const threadId = options?.threadId;
|
|
16388
|
+
if (durable === void 0 || threadId === void 0) {
|
|
16389
|
+
const reason = "runUntil() called with no goal and no threadId \u2014 nothing to resolve a durable objective from";
|
|
16390
|
+
yield { type: "status_change", status: "paused", reason };
|
|
16391
|
+
return { status: "paused", turnsUsed: 0, finalResponse: void 0 };
|
|
16392
|
+
}
|
|
16393
|
+
const { resolveDurableRun: resolveDurableRun2, persistDurableProgress: persistDurableProgress2 } = await Promise.resolve().then(() => (init_local_agent_goal_extensions(), local_agent_goal_extensions_exports));
|
|
16394
|
+
const resolved = await resolveDurableRun2(durable.handle, durable.goalConfig, threadId, options);
|
|
16395
|
+
if (resolved.kind !== "run") {
|
|
16396
|
+
yield {
|
|
16397
|
+
type: "status_change",
|
|
16398
|
+
status: "paused",
|
|
16399
|
+
reason: pausedReason(resolved.kind, threadId)
|
|
16400
|
+
};
|
|
16401
|
+
return { status: "paused", turnsUsed: 0, finalResponse: void 0 };
|
|
16402
|
+
}
|
|
16403
|
+
const result = yield* runUntilImpl2(agent, resolved.goal, resolved.options, await buildDeps());
|
|
16404
|
+
await persistDurableProgress2(durable.handle, threadId, result);
|
|
16405
|
+
return result;
|
|
16084
16406
|
}
|
|
16085
16407
|
return wrap();
|
|
16086
16408
|
}
|
|
@@ -16146,6 +16468,44 @@ function ponyfillAny(signals) {
|
|
|
16146
16468
|
return ctrl.signal;
|
|
16147
16469
|
}
|
|
16148
16470
|
|
|
16471
|
+
// src/internal/runtime/lifecycle/wrap-completion-check-run.ts
|
|
16472
|
+
function wrapRunWithCompletionCheck(args) {
|
|
16473
|
+
const check = args.completionCheck;
|
|
16474
|
+
if (check === void 0) return args.run;
|
|
16475
|
+
const compute = async () => {
|
|
16476
|
+
const result = await args.run.wait();
|
|
16477
|
+
if (result.status !== "finished" || result.result === void 0) return result;
|
|
16478
|
+
const judgeOpts = {};
|
|
16479
|
+
if (check.judgeModel !== void 0) judgeOpts.judgeModel = check.judgeModel;
|
|
16480
|
+
if (check.apiKey !== void 0) judgeOpts.apiKey = check.apiKey;
|
|
16481
|
+
const verdict = await args.deps.judge(
|
|
16482
|
+
{ goal: check.criteria, lastResponse: result.result },
|
|
16483
|
+
judgeOpts
|
|
16484
|
+
);
|
|
16485
|
+
const complete = !verdict.parseFailed && verdict.verdict === "done";
|
|
16486
|
+
emitRunEvent(args.onRunEvent, {
|
|
16487
|
+
type: "completion_check",
|
|
16488
|
+
complete,
|
|
16489
|
+
reason: verdict.reason
|
|
16490
|
+
});
|
|
16491
|
+
return {
|
|
16492
|
+
...result,
|
|
16493
|
+
completionCheck: { complete, reason: verdict.reason, parseFailed: verdict.parseFailed }
|
|
16494
|
+
};
|
|
16495
|
+
};
|
|
16496
|
+
let judged;
|
|
16497
|
+
const wrappedWait = () => {
|
|
16498
|
+
judged ??= compute();
|
|
16499
|
+
return judged;
|
|
16500
|
+
};
|
|
16501
|
+
return new Proxy(args.run, {
|
|
16502
|
+
get(target, prop, receiver) {
|
|
16503
|
+
if (prop === "wait") return wrappedWait;
|
|
16504
|
+
return Reflect.get(target, prop, receiver);
|
|
16505
|
+
}
|
|
16506
|
+
});
|
|
16507
|
+
}
|
|
16508
|
+
|
|
16149
16509
|
// src/internal/runtime/processors/run-processors.ts
|
|
16150
16510
|
var ProcessorAbort = class {
|
|
16151
16511
|
constructor(processorId, reason) {
|
|
@@ -16285,6 +16645,9 @@ function wrapRunWithOutputProcessors(args) {
|
|
|
16285
16645
|
});
|
|
16286
16646
|
}
|
|
16287
16647
|
|
|
16648
|
+
// src/internal/runtime/local-agent/local-agent-send.ts
|
|
16649
|
+
init_local_agent_goal_extensions();
|
|
16650
|
+
|
|
16288
16651
|
// src/internal/runtime/local-agent/local-agent-memory-hooks.ts
|
|
16289
16652
|
var DEFAULT_MAX_RECALL_BYTES = 16e3;
|
|
16290
16653
|
async function applyPreUserSendHook(args) {
|
|
@@ -16400,6 +16763,11 @@ async function executeSendLocked(inputs, message, options) {
|
|
|
16400
16763
|
memoryFacts,
|
|
16401
16764
|
activeMemorySummary
|
|
16402
16765
|
);
|
|
16766
|
+
const projectedSystemPrompt = await projectCurrentObjective(
|
|
16767
|
+
inputs.storageHandle,
|
|
16768
|
+
options.objectiveThreadId,
|
|
16769
|
+
assembledSystemPrompt
|
|
16770
|
+
);
|
|
16403
16771
|
const composedOptions = {
|
|
16404
16772
|
...options,
|
|
16405
16773
|
signal: anySignal([options.signal, inputs.lifecycleAbortController.signal])
|
|
@@ -16407,7 +16775,7 @@ async function executeSendLocked(inputs, message, options) {
|
|
|
16407
16775
|
const run = await inputs.dispatchRun(
|
|
16408
16776
|
adaptedMessage,
|
|
16409
16777
|
composedOptions,
|
|
16410
|
-
|
|
16778
|
+
projectedSystemPrompt,
|
|
16411
16779
|
memoryFacts,
|
|
16412
16780
|
priorMessages,
|
|
16413
16781
|
memoryTools,
|
|
@@ -16420,18 +16788,43 @@ async function executeSendLocked(inputs, message, options) {
|
|
|
16420
16788
|
agentId: inputs.agentId,
|
|
16421
16789
|
onRunEvent: options.onRunEvent
|
|
16422
16790
|
}) : run;
|
|
16423
|
-
|
|
16791
|
+
const hookedRun = wrapRunWithPostReplyHook({
|
|
16424
16792
|
pluginManager: inputs.pluginManagerCode,
|
|
16425
16793
|
agentId: inputs.agentId,
|
|
16426
16794
|
options: inputs.options,
|
|
16427
16795
|
run: processedRun,
|
|
16428
16796
|
userText
|
|
16429
16797
|
});
|
|
16798
|
+
return wrapRunWithCompletionCheck({
|
|
16799
|
+
run: hookedRun,
|
|
16800
|
+
completionCheck: options.completionCheck,
|
|
16801
|
+
onRunEvent: options.onRunEvent,
|
|
16802
|
+
deps: buildCompletionCheckDeps()
|
|
16803
|
+
});
|
|
16804
|
+
}
|
|
16805
|
+
function buildCompletionCheckDeps() {
|
|
16806
|
+
return {
|
|
16807
|
+
judge: async (ctx, opts) => {
|
|
16808
|
+
const { judgeCallImpl: judgeCallImpl2 } = await Promise.resolve().then(() => (init_judge_call(), judge_call_exports));
|
|
16809
|
+
const { getAgentFacade: getAgentFacade2 } = await Promise.resolve().then(() => (init_agent_factory_registry(), agent_factory_registry_exports));
|
|
16810
|
+
return judgeCallImpl2(ctx, opts, { create: getAgentFacade2().create });
|
|
16811
|
+
}
|
|
16812
|
+
};
|
|
16430
16813
|
}
|
|
16431
16814
|
function readMemoryForSend(workspaceCwd, memoryConfig) {
|
|
16432
16815
|
if (memoryConfig?.enabled !== true) return Promise.resolve([]);
|
|
16433
16816
|
return safeCall(() => readMemoryFacts(workspaceCwd, memoryConfig), [], "memory read");
|
|
16434
16817
|
}
|
|
16818
|
+
async function projectCurrentObjective(storageHandle, objectiveThreadId, assembled) {
|
|
16819
|
+
if (objectiveThreadId === void 0) return assembled;
|
|
16820
|
+
const objective = await safeCall(
|
|
16821
|
+
() => resolveCurrentObjectiveText(storageHandle, objectiveThreadId),
|
|
16822
|
+
void 0,
|
|
16823
|
+
"objective projection"
|
|
16824
|
+
);
|
|
16825
|
+
if (objective === void 0) return assembled;
|
|
16826
|
+
return formatObjectiveProjection(objective, assembled);
|
|
16827
|
+
}
|
|
16435
16828
|
|
|
16436
16829
|
// src/internal/runtime/local-agent/local-agent-task-wrap.ts
|
|
16437
16830
|
init_registry();
|
|
@@ -16794,20 +17187,33 @@ var LocalAgent = class {
|
|
|
16794
17187
|
...opts !== void 0 ? { opts } : {}
|
|
16795
17188
|
});
|
|
16796
17189
|
}
|
|
17190
|
+
// biome-ignore format: G8 budget — artifact stubs (local agents have no artifacts); kept 1-line each.
|
|
16797
17191
|
listArtifacts() {
|
|
16798
17192
|
return Promise.resolve([]);
|
|
16799
17193
|
}
|
|
17194
|
+
// biome-ignore format: G8 budget — see listArtifacts.
|
|
16800
17195
|
downloadArtifact(_path) {
|
|
16801
|
-
return Promise.reject(
|
|
16802
|
-
new UnsupportedRunOperationError(
|
|
16803
|
-
"Artifacts are not supported for local agents",
|
|
16804
|
-
"downloadArtifact"
|
|
16805
|
-
)
|
|
16806
|
-
);
|
|
17196
|
+
return Promise.reject(new UnsupportedRunOperationError("Artifacts are not supported for local agents", "downloadArtifact"));
|
|
16807
17197
|
}
|
|
16808
17198
|
// biome-ignore format: G8 budget — both methods delegate to `local-agent-runtime-extensions.ts`; signatures kept as 1-line each.
|
|
16809
17199
|
runUntil(goal, options) {
|
|
16810
|
-
return localAgentRunUntil(this, goal, options);
|
|
17200
|
+
return localAgentRunUntil(this, goal, options, { handle: this.storageHandle(), goalConfig: this.options.goal });
|
|
17201
|
+
}
|
|
17202
|
+
// biome-ignore format: SE33 G8 budget — objective methods delegate to `local-agent-goal-extensions.ts`.
|
|
17203
|
+
setObjective(objective, opts) {
|
|
17204
|
+
return localAgentSetObjective(this.storageHandle(), objective, opts);
|
|
17205
|
+
}
|
|
17206
|
+
// biome-ignore format: SE33 G8 budget — see setObjective above.
|
|
17207
|
+
getObjective(opts) {
|
|
17208
|
+
return localAgentGetObjective(this.storageHandle(), opts);
|
|
17209
|
+
}
|
|
17210
|
+
// biome-ignore format: SE33 G8 budget — see setObjective above.
|
|
17211
|
+
updateObjectiveOptions(opts) {
|
|
17212
|
+
return localAgentUpdateObjectiveOptions(this.storageHandle(), opts);
|
|
17213
|
+
}
|
|
17214
|
+
// biome-ignore format: SE33 G8 budget — see setObjective above.
|
|
17215
|
+
clearObjective(opts) {
|
|
17216
|
+
return localAgentClearObjective(this.storageHandle(), opts);
|
|
16811
17217
|
}
|
|
16812
17218
|
// biome-ignore format: G8 budget — see runUntil comment above.
|
|
16813
17219
|
fork(options) {
|