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