@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.
Files changed (46) hide show
  1. package/CHANGELOG.md +34 -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-BNHJywtl.d.ts} +482 -18
  7. package/dist/{cron-DgEQCJ2i.d.ts → cron-t4oKI2Is.d.cts} +482 -18
  8. package/dist/cron.cjs +1143 -692
  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 +1146 -695
  13. package/dist/cron.js.map +1 -1
  14. package/dist/{errors-DLMNb4Ka.d.cts → errors-DZpCGlYv.d.cts} +1 -1
  15. package/dist/{errors-CbY3pxY7.d.ts → errors-D_Bfo30u.d.ts} +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 +1038 -590
  22. package/dist/index.cjs.map +1 -1
  23. package/dist/index.d.cts +13 -135
  24. package/dist/index.d.ts +13 -135
  25. package/dist/index.js +1037 -588
  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-CLXKMRgq.d.cts} +71 -2
  37. package/dist/{run-CdWiihyU.d.ts → run-CLXKMRgq.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/cron.d.ts +30 -13
  41. package/dist/types/goal-events.d.ts +7 -0
  42. package/dist/types/index.d.ts +1 -0
  43. package/dist/types/objective.d.ts +45 -0
  44. package/dist/types/run-events.d.ts +12 -1
  45. package/dist/types/run.d.ts +58 -0
  46. package/package.json +3 -3
package/dist/cron.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');
@@ -783,6 +783,155 @@ var init_cwd_mutex = __esm({
783
783
  tails = /* @__PURE__ */ new Map();
784
784
  }
785
785
  });
786
+ function safePathJoin(base, ...parts) {
787
+ if (base === "") {
788
+ throw new Error("safePathJoin: base must be non-empty");
789
+ }
790
+ rejectNulAndControlChars(base, "base");
791
+ for (const part of parts) {
792
+ rejectNulAndControlChars(part, "path segment");
793
+ }
794
+ const baseResolved = path.resolve(base);
795
+ const target = path.resolve(base, ...parts);
796
+ if (target !== baseResolved && !target.startsWith(baseResolved + path.sep)) {
797
+ throw new PathTraversalError(parts.join("/"), target);
798
+ }
799
+ return target;
800
+ }
801
+ function rejectNulAndControlChars(input, role) {
802
+ for (let i = 0; i < input.length; i++) {
803
+ const code = input.charCodeAt(i);
804
+ if (code === 0 || code >= 1 && code <= 31 || code === 127) {
805
+ const label = code === 0 ? "<nul-byte>" : `<control-char-0x${code.toString(16)}>`;
806
+ throw new PathTraversalError(`${role}: ${input}`, label);
807
+ }
808
+ }
809
+ }
810
+ function assertNoSymlinkEscape(path$1, base) {
811
+ rejectNulAndControlChars(path$1, "path");
812
+ rejectNulAndControlChars(base, "base");
813
+ let baseResolved;
814
+ try {
815
+ baseResolved = fs.realpathSync(base);
816
+ } catch {
817
+ baseResolved = path.resolve(base);
818
+ }
819
+ const resolved = realpathOfDeepestExisting(path$1);
820
+ if (resolved === void 0) return;
821
+ if (resolved !== baseResolved && !resolved.startsWith(baseResolved + path.sep)) {
822
+ throw new PathTraversalError(`symlink ${path$1}`, resolved);
823
+ }
824
+ }
825
+ function realpathOfDeepestExisting(path$1) {
826
+ try {
827
+ return fs.realpathSync(path$1);
828
+ } catch {
829
+ }
830
+ try {
831
+ const stat6 = fs.lstatSync(path$1);
832
+ if (stat6.isSymbolicLink()) {
833
+ const target = fs.readlinkSync(path$1);
834
+ const parentReal = realpathOfDeepestExisting(path.dirname(path$1));
835
+ const parentBase = parentReal ?? path.dirname(path$1);
836
+ return path.resolve(parentBase, target);
837
+ }
838
+ } catch {
839
+ }
840
+ let cursor = path.dirname(path$1);
841
+ let suffix = path$1.slice(cursor.length);
842
+ while (cursor !== path.dirname(cursor)) {
843
+ try {
844
+ const real = fs.realpathSync(cursor);
845
+ return path.resolve(real, `.${suffix}`);
846
+ } catch {
847
+ suffix = path$1.slice(path.dirname(cursor).length);
848
+ cursor = path.dirname(cursor);
849
+ }
850
+ }
851
+ return void 0;
852
+ }
853
+ function validateArtifactPath(input) {
854
+ rejectKnownPrefixVectors(input);
855
+ const normalized = decodeAndNormalize(input);
856
+ rejectParentTraversal(input, normalized);
857
+ }
858
+ function rejectKnownPrefixVectors(input) {
859
+ if (input.includes("\0")) {
860
+ throw new PathTraversalError(input, "<nul-byte>");
861
+ }
862
+ if (input.startsWith("/") || input.startsWith("~")) {
863
+ throw new PathTraversalError(input, input);
864
+ }
865
+ if (/^[A-Za-z]:[\\/]?/.test(input)) {
866
+ throw new PathTraversalError(input, input);
867
+ }
868
+ }
869
+ function decodeAndNormalize(input) {
870
+ let decoded = input;
871
+ for (let i = 0; i < 2; i += 1) {
872
+ try {
873
+ const next = decodeURIComponent(decoded);
874
+ if (next === decoded) break;
875
+ decoded = next;
876
+ } catch {
877
+ throw new PathTraversalError(input, "<malformed-url-encoding>");
878
+ }
879
+ }
880
+ return decoded.replace(/\\/g, "/");
881
+ }
882
+ function rejectParentTraversal(input, normalized) {
883
+ for (const segment of normalized.split("/")) {
884
+ if (segment === ".." || segment === "..%00") {
885
+ throw new PathTraversalError(input, normalized);
886
+ }
887
+ }
888
+ if (normalized.includes("..")) {
889
+ throw new PathTraversalError(input, normalized);
890
+ }
891
+ }
892
+ function sanitizeIdentifier(input, options) {
893
+ const maxLen = options?.maxLen ?? 64;
894
+ if (input.length === 0 || input.length > maxLen) {
895
+ throw new ConfigurationError(`Identifier length out of range (1-${maxLen}): "${input}"`, {
896
+ code: "invalid_identifier"
897
+ });
898
+ }
899
+ rejectNulAndControlChars(input, "identifier");
900
+ if (!IDENTIFIER_PATTERN.test(input)) {
901
+ throw new ConfigurationError(`Identifier contains invalid characters: "${input}"`, {
902
+ code: "invalid_identifier"
903
+ });
904
+ }
905
+ return input.toLowerCase();
906
+ }
907
+ function safeFilenameForId(id, options) {
908
+ if (id.length === 0) {
909
+ throw new ConfigurationError("Filename id must be a non-empty string", {
910
+ code: "invalid_filename_id"
911
+ });
912
+ }
913
+ const maxLen = options?.maxLen ?? 128;
914
+ const lower = id.toLowerCase();
915
+ if (lower.length <= maxLen && IDENTIFIER_PATTERN.test(lower)) {
916
+ return lower;
917
+ }
918
+ return `h-${crypto.createHash("sha256").update(id).digest("hex").slice(0, 16)}`;
919
+ }
920
+ var PathTraversalError, IDENTIFIER_PATTERN;
921
+ var init_path_guard = __esm({
922
+ "src/internal/security/path-guard.ts"() {
923
+ init_errors();
924
+ PathTraversalError = class extends ConfigurationError {
925
+ name = "PathTraversalError";
926
+ constructor(input, resolvedPath) {
927
+ super(`Path traversal attempt: ${input} \u2192 ${resolvedPath}`, {
928
+ code: "path_traversal"
929
+ });
930
+ }
931
+ };
932
+ IDENTIFIER_PATTERN = /^[a-z0-9][a-z0-9\-_]*$/i;
933
+ }
934
+ });
786
935
  function detectNetworkFsName(typeMagic) {
787
936
  return NETWORK_FS_MAGIC.get(typeMagic) ?? null;
788
937
  }
@@ -847,6 +996,80 @@ var init_atomic_write = __esm({
847
996
  }
848
997
  });
849
998
 
999
+ // src/internal/security/index.ts
1000
+ var init_security = __esm({
1001
+ "src/internal/security/index.ts"() {
1002
+ init_path_guard();
1003
+ init_redact();
1004
+ }
1005
+ });
1006
+
1007
+ // src/internal/persistence/file-lock.ts
1008
+ async function getProperLockfile() {
1009
+ if (cached !== void 0) return cached;
1010
+ try {
1011
+ const mod = await import('proper-lockfile');
1012
+ if (!validateLockModule(mod)) {
1013
+ if (!warnedStructural) {
1014
+ warnedStructural = true;
1015
+ process.stderr.write(
1016
+ "[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"
1017
+ );
1018
+ }
1019
+ cached = null;
1020
+ return cached;
1021
+ }
1022
+ cached = mod;
1023
+ } catch {
1024
+ cached = null;
1025
+ }
1026
+ return cached;
1027
+ }
1028
+ function validateLockModule(mod) {
1029
+ if (mod === null || mod === void 0 || typeof mod !== "object") return false;
1030
+ const m = mod;
1031
+ return typeof m.lock === "function" && typeof m.unlock === "function";
1032
+ }
1033
+ async function withFileLock(path, fn, options) {
1034
+ const lib = await getProperLockfile();
1035
+ if (lib === null) {
1036
+ if (!warnedMissing) {
1037
+ warnedMissing = true;
1038
+ process.stderr.write(
1039
+ "[theokit-sdk] proper-lockfile not installed; cross-process file lock unavailable. Install with: pnpm add proper-lockfile\n"
1040
+ );
1041
+ }
1042
+ return withCwdMutex(`file-lock:${path}`, fn);
1043
+ }
1044
+ return withCwdMutex(`file-lock:${path}`, async () => {
1045
+ const release = await lib.lock(path, {
1046
+ // EC-1: companion lockfile, target path may not exist yet.
1047
+ lockfilePath: `${path}.lock`,
1048
+ realpath: false,
1049
+ stale: 3e4,
1050
+ retries: {
1051
+ retries: 5,
1052
+ factor: 1.5,
1053
+ minTimeout: 100,
1054
+ maxTimeout: 5e3
1055
+ }
1056
+ });
1057
+ try {
1058
+ return await fn();
1059
+ } finally {
1060
+ await release();
1061
+ }
1062
+ });
1063
+ }
1064
+ var cached, warnedMissing, warnedStructural;
1065
+ var init_file_lock = __esm({
1066
+ "src/internal/persistence/file-lock.ts"() {
1067
+ init_cwd_mutex();
1068
+ warnedMissing = false;
1069
+ warnedStructural = false;
1070
+ }
1071
+ });
1072
+
850
1073
  // src/internal/runtime/context/yaml-frontmatter.ts
851
1074
  function parseSimpleYaml(text) {
852
1075
  const fields = {};
@@ -1055,37 +1278,402 @@ var init_hooks_source = __esm({
1055
1278
  warned = /* @__PURE__ */ new Set();
1056
1279
  }
1057
1280
  });
1058
- async function withToolWhitelist(whitelist, fn) {
1059
- return toolWhitelistStore.run(whitelist, fn);
1060
- }
1061
- function currentToolWhitelist() {
1062
- return toolWhitelistStore.getStore();
1281
+ function sessionFilePath(cwd, agentId) {
1282
+ const safe2 = sanitizeIdentifier(agentId, { maxLen: 128 });
1283
+ return safePathJoin(cwd, ".theokit", "agents", safe2, "messages.jsonl");
1063
1284
  }
1064
- function checkToolWhitelist(toolName) {
1065
- const whitelist = currentToolWhitelist();
1066
- if (whitelist === void 0) return { allowed: true };
1067
- if (!whitelist.has(toolName)) {
1068
- return {
1069
- allowed: false,
1070
- reason: `Tool "${toolName}" not available in this fork context`
1071
- };
1285
+ async function readJsonlLines(cwd, agentId) {
1286
+ const path = sessionFilePath(cwd, agentId);
1287
+ try {
1288
+ const raw = await promises.readFile(path, "utf8");
1289
+ return raw.split("\n").filter((line) => line.length > 0);
1290
+ } catch {
1291
+ return [];
1072
1292
  }
1073
- return { allowed: true };
1074
1293
  }
1075
- var toolWhitelistStore;
1076
- var init_async_local_storage = __esm({
1077
- "src/internal/runtime/concurrency/async-local-storage.ts"() {
1078
- toolWhitelistStore = new async_hooks.AsyncLocalStorage();
1294
+ function warnMalformed(agentId, line) {
1295
+ process.stderr.write(
1296
+ `[theokit-sdk] skipping malformed line in messages.jsonl (${agentId}): ${line.slice(0, 80)}...
1297
+ `
1298
+ );
1299
+ }
1300
+ function hydrateSessionLine(parsed) {
1301
+ if (typeof parsed.text !== "string" || parsed.role === void 0) return void 0;
1302
+ if (parsed.role === "user" || parsed.role === "assistant") {
1303
+ return { role: parsed.role, text: parsed.text };
1079
1304
  }
1080
- });
1081
-
1082
- // src/internal/runtime/concurrency/async-semaphore.ts
1083
- function createSemaphore(permits) {
1084
- if (!Number.isInteger(permits) || permits < 1) {
1085
- throw new ConfigurationError(
1086
- `async-semaphore: permits must be a positive integer, got ${permits}`,
1087
- { code: "invalid_concurrency" }
1088
- );
1305
+ if (parsed.role === "tool_call" || parsed.role === "tool_result") {
1306
+ const label = parsed.role === "tool_call" ? "tool call" : "tool result";
1307
+ return { role: "assistant", text: `[${label}] ${parsed.text}` };
1308
+ }
1309
+ return void 0;
1310
+ }
1311
+ async function readSessionFile(cwd, agentId) {
1312
+ const lines = await readJsonlLines(cwd, agentId);
1313
+ const messages = [];
1314
+ for (const line of lines) {
1315
+ try {
1316
+ const msg = hydrateSessionLine(JSON.parse(line));
1317
+ if (msg !== void 0) messages.push(msg);
1318
+ } catch {
1319
+ warnMalformed(agentId, line);
1320
+ }
1321
+ }
1322
+ return messages;
1323
+ }
1324
+ async function readAllPersistedMessages(cwd, agentId) {
1325
+ const lines = await readJsonlLines(cwd, agentId);
1326
+ const messages = [];
1327
+ for (const line of lines) {
1328
+ try {
1329
+ const parsed = JSON.parse(line);
1330
+ if (parsed.role !== void 0 && VALID_ROLES.has(parsed.role) && typeof parsed.text === "string") {
1331
+ messages.push({
1332
+ role: parsed.role,
1333
+ text: parsed.text,
1334
+ at: typeof parsed.at === "number" ? parsed.at : Date.now()
1335
+ });
1336
+ }
1337
+ } catch {
1338
+ warnMalformed(agentId, line);
1339
+ }
1340
+ }
1341
+ return messages;
1342
+ }
1343
+ async function appendAnyPersistedMessage(cwd, agentId, record) {
1344
+ await appendPersistedMessages(cwd, agentId, [record]);
1345
+ }
1346
+ async function appendPersistedMessages(cwd, agentId, records) {
1347
+ if (records.length === 0) return;
1348
+ const path$1 = sessionFilePath(cwd, agentId);
1349
+ const payload = records.map((r) => `${redactSecrets(JSON.stringify(r))}
1350
+ `).join("");
1351
+ const dir = path.dirname(path$1);
1352
+ let written = false;
1353
+ const attempt = async () => {
1354
+ await promises.mkdir(dir, { recursive: true });
1355
+ await withFileLock(path$1, async () => {
1356
+ await promises.appendFile(path$1, payload, "utf8");
1357
+ written = true;
1358
+ });
1359
+ };
1360
+ try {
1361
+ await attempt();
1362
+ } catch (cause) {
1363
+ if (written || cause.code !== "ENOENT") throw cause;
1364
+ await attempt();
1365
+ }
1366
+ }
1367
+ async function rewriteLockedSession(path, transform) {
1368
+ await withFileLock(path, async () => {
1369
+ let raw;
1370
+ try {
1371
+ raw = await promises.readFile(path, "utf8");
1372
+ } catch {
1373
+ return;
1374
+ }
1375
+ const lines = raw.split("\n").filter((line) => line.length > 0);
1376
+ const next = transform(lines);
1377
+ if (next === void 0) return;
1378
+ await replaceFileAtomic(path, next);
1379
+ });
1380
+ }
1381
+ async function compactSessionFile(cwd, agentId, maxTurns) {
1382
+ const path = sessionFilePath(cwd, agentId);
1383
+ if (!fs.existsSync(path)) return;
1384
+ await rewriteLockedSession(
1385
+ path,
1386
+ (lines) => lines.length <= maxTurns * 2 ? void 0 : `${lines.slice(-maxTurns).join("\n")}
1387
+ `
1388
+ );
1389
+ }
1390
+ async function truncateSessionTo(cwd, agentId, keepCount) {
1391
+ const path = sessionFilePath(cwd, agentId);
1392
+ if (!fs.existsSync(path)) return 0;
1393
+ let kept = 0;
1394
+ await rewriteLockedSession(path, (lines) => {
1395
+ const keep = Math.max(0, Math.min(keepCount, lines.length));
1396
+ kept = keep;
1397
+ if (keep === lines.length) return void 0;
1398
+ return keep === 0 ? "" : `${lines.slice(0, keep).join("\n")}
1399
+ `;
1400
+ });
1401
+ return kept;
1402
+ }
1403
+ var VALID_ROLES;
1404
+ var init_agent_session_store = __esm({
1405
+ "src/internal/runtime/session/agent-session-store.ts"() {
1406
+ init_atomic_write();
1407
+ init_file_lock();
1408
+ init_security();
1409
+ VALID_ROLES = /* @__PURE__ */ new Set([
1410
+ "user",
1411
+ "assistant",
1412
+ "system",
1413
+ "tool_call",
1414
+ "tool_result"
1415
+ ]);
1416
+ }
1417
+ });
1418
+
1419
+ // src/internal/persistence/objective-coerce.ts
1420
+ function coerceOptions(raw) {
1421
+ if (typeof raw !== "object" || raw === null) return void 0;
1422
+ const o = raw;
1423
+ const out = {};
1424
+ if (typeof o.maxRuns === "number") out.maxRuns = o.maxRuns;
1425
+ if (typeof o.judgeModel === "string") out.judgeModel = o.judgeModel;
1426
+ if (typeof o.prompt === "string") out.prompt = o.prompt;
1427
+ return Object.keys(out).length > 0 ? out : void 0;
1428
+ }
1429
+ function coerceObjectiveRecord(raw) {
1430
+ if (typeof raw !== "object" || raw === null) return void 0;
1431
+ const o = raw;
1432
+ if (o._schemaVersion !== 1) return void 0;
1433
+ if (typeof o.objective !== "string") return void 0;
1434
+ if (typeof o.runsUsed !== "number") return void 0;
1435
+ if (typeof o.status !== "string" || !STATUSES.includes(o.status))
1436
+ return void 0;
1437
+ const options = coerceOptions(o.options);
1438
+ return {
1439
+ _schemaVersion: 1,
1440
+ objective: o.objective,
1441
+ ...options !== void 0 ? { options } : {},
1442
+ status: o.status,
1443
+ runsUsed: o.runsUsed
1444
+ };
1445
+ }
1446
+ var STATUSES;
1447
+ var init_objective_coerce = __esm({
1448
+ "src/internal/persistence/objective-coerce.ts"() {
1449
+ STATUSES = ["active", "done", "paused"];
1450
+ }
1451
+ });
1452
+
1453
+ // src/internal/persistence/pagination.ts
1454
+ function paginate(items, opts) {
1455
+ if (opts === void 0 || opts.offset === void 0 && opts.limit === void 0) return items;
1456
+ const start = Math.max(0, opts.offset ?? 0);
1457
+ const end = opts.limit === void 0 ? items.length : start + Math.max(0, opts.limit);
1458
+ return items.slice(start, end);
1459
+ }
1460
+ var init_pagination = __esm({
1461
+ "src/internal/persistence/pagination.ts"() {
1462
+ }
1463
+ });
1464
+
1465
+ // src/internal/persistence/session-meta.ts
1466
+ function applyMetaPatch(current, patch) {
1467
+ const next = {};
1468
+ if (current.title !== void 0) next.title = current.title;
1469
+ if (current.tag !== void 0) next.tag = current.tag;
1470
+ if (patch.title === null) delete next.title;
1471
+ else if (patch.title !== void 0) next.title = patch.title;
1472
+ if (patch.tag === null) delete next.tag;
1473
+ else if (patch.tag !== void 0) next.tag = patch.tag;
1474
+ return next;
1475
+ }
1476
+ function coerceSessionMeta(raw) {
1477
+ if (typeof raw !== "object" || raw === null) return void 0;
1478
+ const obj = raw;
1479
+ const meta = {};
1480
+ if (typeof obj.title === "string") meta.title = obj.title;
1481
+ if (typeof obj.tag === "string") meta.tag = obj.tag;
1482
+ return meta.title === void 0 && meta.tag === void 0 ? void 0 : meta;
1483
+ }
1484
+ var init_session_meta = __esm({
1485
+ "src/internal/persistence/session-meta.ts"() {
1486
+ }
1487
+ });
1488
+ function toStoredMessage(record) {
1489
+ return {
1490
+ role: record.role,
1491
+ content: record.text,
1492
+ at: record.at
1493
+ };
1494
+ }
1495
+ function toRecord(message) {
1496
+ return { role: message.role, text: message.content, at: message.at ?? Date.now() };
1497
+ }
1498
+ var FileSystemConversationStorage;
1499
+ var init_conversation_storage_fs = __esm({
1500
+ "src/internal/persistence/conversation-storage-fs.ts"() {
1501
+ init_agent_session_store();
1502
+ init_security();
1503
+ init_path_guard();
1504
+ init_file_lock();
1505
+ init_objective_coerce();
1506
+ init_pagination();
1507
+ init_session_meta();
1508
+ FileSystemConversationStorage = class {
1509
+ #root;
1510
+ constructor(opts = {}) {
1511
+ this.#root = opts.root ?? process.cwd();
1512
+ }
1513
+ /** Exposed for tests + diagnostics. The path is sanitized at use sites. */
1514
+ get root() {
1515
+ return this.#root;
1516
+ }
1517
+ async getMessages(conversationId, opts) {
1518
+ const records = await readAllPersistedMessages(this.#root, conversationId);
1519
+ const all = records.map(toStoredMessage);
1520
+ return paginate(all, opts);
1521
+ }
1522
+ async appendMessage(conversationId, message) {
1523
+ await appendAnyPersistedMessage(this.#root, conversationId, toRecord(message));
1524
+ }
1525
+ async appendMessages(conversationId, messages) {
1526
+ await appendPersistedMessages(this.#root, conversationId, messages.map(toRecord));
1527
+ }
1528
+ async truncateConversation(conversationId, keepCount) {
1529
+ return truncateSessionTo(this.#root, conversationId, keepCount);
1530
+ }
1531
+ async deleteConversation(conversationId) {
1532
+ const safe2 = sanitizeIdentifier(conversationId, { maxLen: 128 });
1533
+ const dirPath = safePathJoin(this.#root, ".theokit", "agents", safe2);
1534
+ await promises.rm(dirPath, { recursive: true, force: true });
1535
+ }
1536
+ async deleteScope(prefix) {
1537
+ const ids = await this.listConversationIds();
1538
+ const matching = ids.filter((id) => id.startsWith(prefix));
1539
+ for (const id of matching) await this.deleteConversation(id);
1540
+ return matching.length;
1541
+ }
1542
+ async listConversationIds(opts = {}) {
1543
+ const agentsRoot = safePathJoin(this.#root, ".theokit", "agents");
1544
+ let entries;
1545
+ try {
1546
+ entries = await promises.readdir(agentsRoot);
1547
+ } catch (cause) {
1548
+ if (cause.code === "ENOENT") return [];
1549
+ throw cause;
1550
+ }
1551
+ if (opts.limit !== void 0) return entries.slice(0, opts.limit);
1552
+ return entries;
1553
+ }
1554
+ async compact(conversationId, maxTurns) {
1555
+ await compactSessionFile(this.#root, conversationId, maxTurns);
1556
+ }
1557
+ // SE4 — session metadata persisted as a per-conversation sidecar
1558
+ // `<root>/.theokit/agents/<safeId>/session.json` (same sanitized perimeter as
1559
+ // the transcript). Kept separate from messages.jsonl so a title/tag write does
1560
+ // not rewrite the append-only log.
1561
+ #metaPath(conversationId) {
1562
+ const safe2 = sanitizeIdentifier(conversationId, { maxLen: 128 });
1563
+ return safePathJoin(this.#root, ".theokit", "agents", safe2, "session.json");
1564
+ }
1565
+ async getSessionMeta(conversationId) {
1566
+ try {
1567
+ const raw = await promises.readFile(this.#metaPath(conversationId), "utf8");
1568
+ return coerceSessionMeta(JSON.parse(raw));
1569
+ } catch (cause) {
1570
+ if (cause.code === "ENOENT") return void 0;
1571
+ throw cause;
1572
+ }
1573
+ }
1574
+ async setSessionMeta(conversationId, patch) {
1575
+ const metaPath = this.#metaPath(conversationId);
1576
+ await promises.mkdir(path.dirname(metaPath), { recursive: true });
1577
+ await withFileLock(metaPath, async () => {
1578
+ let current = {};
1579
+ try {
1580
+ current = coerceSessionMeta(JSON.parse(await promises.readFile(metaPath, "utf8"))) ?? {};
1581
+ } catch (cause) {
1582
+ if (cause.code !== "ENOENT") throw cause;
1583
+ }
1584
+ const next = applyMetaPatch(current, patch);
1585
+ await promises.writeFile(metaPath, redactSecrets(JSON.stringify(next)), "utf8");
1586
+ });
1587
+ }
1588
+ // SE33 — the durable objective is kept in `objective.json` beside the
1589
+ // transcript (separate from messages.jsonl + session.json). Uses the TOTAL
1590
+ // `safeFilenameForId` (not `sanitizeIdentifier`) so a caller-supplied
1591
+ // `threadId` with exotic characters (e.g. "user@example.com") hashes to a
1592
+ // deterministic dir instead of throwing — honoring the objective methods'
1593
+ // never-throw contract (ADR D6). Conforming ids pass through unchanged, so
1594
+ // the objective still sits beside the transcript for the normal case.
1595
+ #objectivePath(conversationId) {
1596
+ const safe2 = safeFilenameForId(conversationId, { maxLen: 128 });
1597
+ return safePathJoin(this.#root, ".theokit", "agents", safe2, "objective.json");
1598
+ }
1599
+ async getObjectiveRecord(conversationId) {
1600
+ try {
1601
+ const raw = await promises.readFile(this.#objectivePath(conversationId), "utf8");
1602
+ return coerceObjectiveRecord(JSON.parse(raw));
1603
+ } catch (cause) {
1604
+ if (cause.code === "ENOENT") return void 0;
1605
+ throw cause;
1606
+ }
1607
+ }
1608
+ async setObjectiveRecord(conversationId, record) {
1609
+ const path$1 = this.#objectivePath(conversationId);
1610
+ if (record === null) {
1611
+ await promises.rm(path$1, { force: true });
1612
+ return;
1613
+ }
1614
+ await promises.mkdir(path.dirname(path$1), { recursive: true });
1615
+ await withFileLock(path$1, async () => {
1616
+ await promises.writeFile(path$1, redactSecrets(JSON.stringify(record)), "utf8");
1617
+ });
1618
+ }
1619
+ // SE33 (HIGH-1 fix) — atomic read-modify-write: the read that feeds `mutate`
1620
+ // happens INSIDE the same file lock as the write, so two concurrent progress
1621
+ // write-backs on one thread cannot both read a stale `runsUsed` and drop turns.
1622
+ async updateObjectiveRecord(conversationId, mutate) {
1623
+ const path$1 = this.#objectivePath(conversationId);
1624
+ await promises.mkdir(path.dirname(path$1), { recursive: true });
1625
+ await withFileLock(path$1, async () => {
1626
+ let current;
1627
+ try {
1628
+ current = coerceObjectiveRecord(JSON.parse(await promises.readFile(path$1, "utf8")));
1629
+ } catch (cause) {
1630
+ if (cause.code !== "ENOENT") throw cause;
1631
+ }
1632
+ const next = mutate(current);
1633
+ if (next === void 0) return;
1634
+ if (next === null) {
1635
+ await promises.rm(path$1, { force: true });
1636
+ return;
1637
+ }
1638
+ await promises.writeFile(path$1, redactSecrets(JSON.stringify(next)), "utf8");
1639
+ });
1640
+ }
1641
+ async dispose() {
1642
+ }
1643
+ };
1644
+ }
1645
+ });
1646
+ async function withToolWhitelist(whitelist, fn) {
1647
+ return toolWhitelistStore.run(whitelist, fn);
1648
+ }
1649
+ function currentToolWhitelist() {
1650
+ return toolWhitelistStore.getStore();
1651
+ }
1652
+ function checkToolWhitelist(toolName) {
1653
+ const whitelist = currentToolWhitelist();
1654
+ if (whitelist === void 0) return { allowed: true };
1655
+ if (!whitelist.has(toolName)) {
1656
+ return {
1657
+ allowed: false,
1658
+ reason: `Tool "${toolName}" not available in this fork context`
1659
+ };
1660
+ }
1661
+ return { allowed: true };
1662
+ }
1663
+ var toolWhitelistStore;
1664
+ var init_async_local_storage = __esm({
1665
+ "src/internal/runtime/concurrency/async-local-storage.ts"() {
1666
+ toolWhitelistStore = new async_hooks.AsyncLocalStorage();
1667
+ }
1668
+ });
1669
+
1670
+ // src/internal/runtime/concurrency/async-semaphore.ts
1671
+ function createSemaphore(permits) {
1672
+ if (!Number.isInteger(permits) || permits < 1) {
1673
+ throw new ConfigurationError(
1674
+ `async-semaphore: permits must be a positive integer, got ${permits}`,
1675
+ { code: "invalid_concurrency" }
1676
+ );
1089
1677
  }
1090
1678
  let active = 0;
1091
1679
  const queue = [];
@@ -1428,6 +2016,151 @@ var init_credential_pool_context = __esm({
1428
2016
  }
1429
2017
  });
1430
2018
 
2019
+ // src/internal/runtime/objective/objective-store.ts
2020
+ function canPersist(a) {
2021
+ return typeof a.getObjectiveRecord === "function" && typeof a.setObjectiveRecord === "function";
2022
+ }
2023
+ async function mutateObjective(adapter, conversationId, mutate) {
2024
+ if (typeof adapter.updateObjectiveRecord === "function") {
2025
+ await adapter.updateObjectiveRecord(conversationId, mutate);
2026
+ return;
2027
+ }
2028
+ const current = await adapter.getObjectiveRecord(conversationId);
2029
+ const next = mutate(current);
2030
+ if (next === void 0) return;
2031
+ await adapter.setObjectiveRecord(conversationId, next);
2032
+ }
2033
+ async function getObjective(adapter, conversationId) {
2034
+ if (!canPersist(adapter)) return void 0;
2035
+ return adapter.getObjectiveRecord(conversationId);
2036
+ }
2037
+ async function setObjective(adapter, conversationId, objective, options) {
2038
+ if (!canPersist(adapter)) return;
2039
+ const record = {
2040
+ _schemaVersion: 1,
2041
+ objective,
2042
+ ...options !== void 0 ? { options } : {},
2043
+ status: "active",
2044
+ runsUsed: 0
2045
+ };
2046
+ await adapter.setObjectiveRecord(conversationId, record);
2047
+ }
2048
+ async function updateObjectiveOptions(adapter, conversationId, patch) {
2049
+ if (!canPersist(adapter)) return;
2050
+ await mutateObjective(
2051
+ adapter,
2052
+ conversationId,
2053
+ (current) => current === void 0 ? void 0 : { ...current, options: { ...current.options, ...patch } }
2054
+ );
2055
+ }
2056
+ async function writeObjectiveProgress(adapter, conversationId, progress) {
2057
+ if (!canPersist(adapter)) return;
2058
+ await mutateObjective(
2059
+ adapter,
2060
+ conversationId,
2061
+ (current) => current === void 0 ? void 0 : { ...current, runsUsed: progress.runsUsed, status: progress.status }
2062
+ );
2063
+ }
2064
+ async function clearObjective(adapter, conversationId) {
2065
+ if (!canPersist(adapter)) return;
2066
+ await adapter.setObjectiveRecord(conversationId, null);
2067
+ }
2068
+ var init_objective_store = __esm({
2069
+ "src/internal/runtime/objective/objective-store.ts"() {
2070
+ }
2071
+ });
2072
+
2073
+ // src/internal/runtime/local-agent/local-agent-goal-extensions.ts
2074
+ var local_agent_goal_extensions_exports = {};
2075
+ __export(local_agent_goal_extensions_exports, {
2076
+ formatObjectiveProjection: () => formatObjectiveProjection,
2077
+ localAgentClearObjective: () => localAgentClearObjective,
2078
+ localAgentGetObjective: () => localAgentGetObjective,
2079
+ localAgentSetObjective: () => localAgentSetObjective,
2080
+ localAgentUpdateObjectiveOptions: () => localAgentUpdateObjectiveOptions,
2081
+ persistDurableProgress: () => persistDurableProgress,
2082
+ resolveCurrentObjectiveText: () => resolveCurrentObjectiveText,
2083
+ resolveDurableRun: () => resolveDurableRun
2084
+ });
2085
+ function resolveAdapter(handle) {
2086
+ return typeof handle === "string" ? new FileSystemConversationStorage({ root: handle }) : handle;
2087
+ }
2088
+ function assertValidGoalOptions(options) {
2089
+ if (options.maxRuns !== void 0 && (!Number.isInteger(options.maxRuns) || options.maxRuns <= 0)) {
2090
+ throw new ConfigurationError(`maxRuns must be a positive integer, got ${options.maxRuns}`, {
2091
+ code: "invalid_objective_max_runs"
2092
+ });
2093
+ }
2094
+ }
2095
+ async function localAgentSetObjective(handle, objective, opts) {
2096
+ const { threadId, ...options } = opts;
2097
+ assertValidGoalOptions(options);
2098
+ await setObjective(
2099
+ resolveAdapter(handle),
2100
+ threadId,
2101
+ objective,
2102
+ Object.keys(options).length > 0 ? options : void 0
2103
+ );
2104
+ }
2105
+ async function localAgentGetObjective(handle, opts) {
2106
+ return getObjective(resolveAdapter(handle), opts.threadId);
2107
+ }
2108
+ async function localAgentUpdateObjectiveOptions(handle, opts) {
2109
+ const { threadId, ...patch } = opts;
2110
+ assertValidGoalOptions(patch);
2111
+ await updateObjectiveOptions(resolveAdapter(handle), threadId, patch);
2112
+ }
2113
+ async function localAgentClearObjective(handle, opts) {
2114
+ await clearObjective(resolveAdapter(handle), opts.threadId);
2115
+ }
2116
+ async function resolveCurrentObjectiveText(handle, threadId) {
2117
+ const record = await getObjective(resolveAdapter(handle), threadId);
2118
+ return record?.status === "active" ? record.objective : void 0;
2119
+ }
2120
+ function formatObjectiveProjection(objective, assembled) {
2121
+ const signal = `<current-objective>
2122
+ ${objective}
2123
+ </current-objective>`;
2124
+ return assembled === void 0 || assembled.length === 0 ? signal : `${signal}
2125
+
2126
+ ${assembled}`;
2127
+ }
2128
+ async function resolveDurableRun(handle, goalConfig, threadId, callerOptions) {
2129
+ const record = await getObjective(resolveAdapter(handle), threadId);
2130
+ if (record === void 0) return { kind: "none" };
2131
+ const judgeModel = callerOptions?.judgeModel ?? record.options?.judgeModel ?? goalConfig?.judgeModel;
2132
+ if (judgeModel === void 0) return { kind: "inert" };
2133
+ const totalBudget = record.options?.maxRuns ?? goalConfig?.maxRuns ?? DEFAULT_MAX_RUNS;
2134
+ const remaining = Math.max(0, totalBudget - record.runsUsed);
2135
+ if (remaining === 0) return { kind: "exhausted" };
2136
+ const perCall = callerOptions?.maxTurns;
2137
+ const maxTurns = perCall !== void 0 ? Math.min(perCall, remaining) : remaining;
2138
+ return {
2139
+ kind: "run",
2140
+ goal: record.objective,
2141
+ options: { ...callerOptions, maxTurns, judgeModel }
2142
+ };
2143
+ }
2144
+ async function persistDurableProgress(handle, threadId, result) {
2145
+ const adapter = resolveAdapter(handle);
2146
+ const record = await getObjective(adapter, threadId);
2147
+ if (record === void 0) return;
2148
+ const status = result.status === "completed" ? "done" : result.status === "paused" ? "paused" : "active";
2149
+ await writeObjectiveProgress(adapter, threadId, {
2150
+ runsUsed: record.runsUsed + result.turnsUsed,
2151
+ status
2152
+ });
2153
+ }
2154
+ var DEFAULT_MAX_RUNS;
2155
+ var init_local_agent_goal_extensions = __esm({
2156
+ "src/internal/runtime/local-agent/local-agent-goal-extensions.ts"() {
2157
+ init_errors();
2158
+ init_conversation_storage_fs();
2159
+ init_objective_store();
2160
+ DEFAULT_MAX_RUNS = 20;
2161
+ }
2162
+ });
2163
+
1431
2164
  // src/internal/personality/context.ts
1432
2165
  var context_exports = {};
1433
2166
  __export(context_exports, {
@@ -3218,154 +3951,9 @@ function structurableAnswerOrThrow(result, GenerateObjectError2) {
3218
3951
  // src/internal/runtime/cloud/cloud-agent.ts
3219
3952
  init_errors();
3220
3953
  init_cwd_mutex();
3954
+ init_path_guard();
3221
3955
 
3222
- // src/internal/security/path-guard.ts
3223
- init_errors();
3224
- var PathTraversalError = class extends ConfigurationError {
3225
- name = "PathTraversalError";
3226
- constructor(input, resolvedPath) {
3227
- super(`Path traversal attempt: ${input} \u2192 ${resolvedPath}`, {
3228
- code: "path_traversal"
3229
- });
3230
- }
3231
- };
3232
- function safePathJoin(base, ...parts) {
3233
- if (base === "") {
3234
- throw new Error("safePathJoin: base must be non-empty");
3235
- }
3236
- rejectNulAndControlChars(base, "base");
3237
- for (const part of parts) {
3238
- rejectNulAndControlChars(part, "path segment");
3239
- }
3240
- const baseResolved = path.resolve(base);
3241
- const target = path.resolve(base, ...parts);
3242
- if (target !== baseResolved && !target.startsWith(baseResolved + path.sep)) {
3243
- throw new PathTraversalError(parts.join("/"), target);
3244
- }
3245
- return target;
3246
- }
3247
- function rejectNulAndControlChars(input, role) {
3248
- for (let i = 0; i < input.length; i++) {
3249
- const code = input.charCodeAt(i);
3250
- if (code === 0 || code >= 1 && code <= 31 || code === 127) {
3251
- const label = code === 0 ? "<nul-byte>" : `<control-char-0x${code.toString(16)}>`;
3252
- throw new PathTraversalError(`${role}: ${input}`, label);
3253
- }
3254
- }
3255
- }
3256
- function assertNoSymlinkEscape(path$1, base) {
3257
- rejectNulAndControlChars(path$1, "path");
3258
- rejectNulAndControlChars(base, "base");
3259
- let baseResolved;
3260
- try {
3261
- baseResolved = fs.realpathSync(base);
3262
- } catch {
3263
- baseResolved = path.resolve(base);
3264
- }
3265
- const resolved = realpathOfDeepestExisting(path$1);
3266
- if (resolved === void 0) return;
3267
- if (resolved !== baseResolved && !resolved.startsWith(baseResolved + path.sep)) {
3268
- throw new PathTraversalError(`symlink ${path$1}`, resolved);
3269
- }
3270
- }
3271
- function realpathOfDeepestExisting(path$1) {
3272
- try {
3273
- return fs.realpathSync(path$1);
3274
- } catch {
3275
- }
3276
- try {
3277
- const stat6 = fs.lstatSync(path$1);
3278
- if (stat6.isSymbolicLink()) {
3279
- const target = fs.readlinkSync(path$1);
3280
- const parentReal = realpathOfDeepestExisting(path.dirname(path$1));
3281
- const parentBase = parentReal ?? path.dirname(path$1);
3282
- return path.resolve(parentBase, target);
3283
- }
3284
- } catch {
3285
- }
3286
- let cursor = path.dirname(path$1);
3287
- let suffix = path$1.slice(cursor.length);
3288
- while (cursor !== path.dirname(cursor)) {
3289
- try {
3290
- const real = fs.realpathSync(cursor);
3291
- return path.resolve(real, `.${suffix}`);
3292
- } catch {
3293
- suffix = path$1.slice(path.dirname(cursor).length);
3294
- cursor = path.dirname(cursor);
3295
- }
3296
- }
3297
- return void 0;
3298
- }
3299
- var IDENTIFIER_PATTERN = /^[a-z0-9][a-z0-9\-_]*$/i;
3300
- function validateArtifactPath(input) {
3301
- rejectKnownPrefixVectors(input);
3302
- const normalized = decodeAndNormalize(input);
3303
- rejectParentTraversal(input, normalized);
3304
- }
3305
- function rejectKnownPrefixVectors(input) {
3306
- if (input.includes("\0")) {
3307
- throw new PathTraversalError(input, "<nul-byte>");
3308
- }
3309
- if (input.startsWith("/") || input.startsWith("~")) {
3310
- throw new PathTraversalError(input, input);
3311
- }
3312
- if (/^[A-Za-z]:[\\/]?/.test(input)) {
3313
- throw new PathTraversalError(input, input);
3314
- }
3315
- }
3316
- function decodeAndNormalize(input) {
3317
- let decoded = input;
3318
- for (let i = 0; i < 2; i += 1) {
3319
- try {
3320
- const next = decodeURIComponent(decoded);
3321
- if (next === decoded) break;
3322
- decoded = next;
3323
- } catch {
3324
- throw new PathTraversalError(input, "<malformed-url-encoding>");
3325
- }
3326
- }
3327
- return decoded.replace(/\\/g, "/");
3328
- }
3329
- function rejectParentTraversal(input, normalized) {
3330
- for (const segment of normalized.split("/")) {
3331
- if (segment === ".." || segment === "..%00") {
3332
- throw new PathTraversalError(input, normalized);
3333
- }
3334
- }
3335
- if (normalized.includes("..")) {
3336
- throw new PathTraversalError(input, normalized);
3337
- }
3338
- }
3339
- function sanitizeIdentifier(input, options) {
3340
- const maxLen = options?.maxLen ?? 64;
3341
- if (input.length === 0 || input.length > maxLen) {
3342
- throw new ConfigurationError(`Identifier length out of range (1-${maxLen}): "${input}"`, {
3343
- code: "invalid_identifier"
3344
- });
3345
- }
3346
- rejectNulAndControlChars(input, "identifier");
3347
- if (!IDENTIFIER_PATTERN.test(input)) {
3348
- throw new ConfigurationError(`Identifier contains invalid characters: "${input}"`, {
3349
- code: "invalid_identifier"
3350
- });
3351
- }
3352
- return input.toLowerCase();
3353
- }
3354
- function safeFilenameForId(id, options) {
3355
- if (id.length === 0) {
3356
- throw new ConfigurationError("Filename id must be a non-empty string", {
3357
- code: "invalid_filename_id"
3358
- });
3359
- }
3360
- const maxLen = options?.maxLen;
3361
- const lower = id.toLowerCase();
3362
- if (lower.length <= maxLen && IDENTIFIER_PATTERN.test(lower)) {
3363
- return lower;
3364
- }
3365
- return `h-${crypto.createHash("sha256").update(id).digest("hex").slice(0, 16)}`;
3366
- }
3367
-
3368
- // src/internal/runtime/model-selection.ts
3956
+ // src/internal/runtime/model-selection.ts
3369
3957
  init_errors();
3370
3958
  function normalizeModel(m) {
3371
3959
  if (m === void 0 || typeof m !== "string") return m;
@@ -3853,8 +4441,8 @@ function serializeMemory2(memory) {
3853
4441
  return result;
3854
4442
  }
3855
4443
 
3856
- // src/internal/security/index.ts
3857
- init_redact();
4444
+ // src/internal/runtime/fixtures/fixture-responder.ts
4445
+ init_security();
3858
4446
 
3859
4447
  // src/internal/runtime/fixtures/fixture-events.ts
3860
4448
  function assistantOnlyConversation(text) {
@@ -3973,6 +4561,10 @@ function sanitizeMcpName(name) {
3973
4561
  // src/internal/memory/storage/markdown-store.ts
3974
4562
  init_atomic_write();
3975
4563
  init_cwd_mutex();
4564
+
4565
+ // src/internal/memory/types.ts
4566
+ init_path_guard();
4567
+ init_security();
3976
4568
  function legacyMemoryJsonPath(cwd, config) {
3977
4569
  if (config.storePath !== void 0) {
3978
4570
  return path.resolve(cwd, config.storePath);
@@ -5300,68 +5892,7 @@ init_cwd_mutex();
5300
5892
 
5301
5893
  // src/internal/personality/store.ts
5302
5894
  init_atomic_write();
5303
-
5304
- // src/internal/persistence/file-lock.ts
5305
- init_cwd_mutex();
5306
- var cached;
5307
- var warnedMissing = false;
5308
- var warnedStructural = false;
5309
- async function getProperLockfile() {
5310
- if (cached !== void 0) return cached;
5311
- try {
5312
- const mod = await import('proper-lockfile');
5313
- if (!validateLockModule(mod)) {
5314
- if (!warnedStructural) {
5315
- warnedStructural = true;
5316
- process.stderr.write(
5317
- "[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"
5318
- );
5319
- }
5320
- cached = null;
5321
- return cached;
5322
- }
5323
- cached = mod;
5324
- } catch {
5325
- cached = null;
5326
- }
5327
- return cached;
5328
- }
5329
- function validateLockModule(mod) {
5330
- if (mod === null || mod === void 0 || typeof mod !== "object") return false;
5331
- const m = mod;
5332
- return typeof m.lock === "function" && typeof m.unlock === "function";
5333
- }
5334
- async function withFileLock(path, fn, options) {
5335
- const lib = await getProperLockfile();
5336
- if (lib === null) {
5337
- if (!warnedMissing) {
5338
- warnedMissing = true;
5339
- process.stderr.write(
5340
- "[theokit-sdk] proper-lockfile not installed; cross-process file lock unavailable. Install with: pnpm add proper-lockfile\n"
5341
- );
5342
- }
5343
- return withCwdMutex(`file-lock:${path}`, fn);
5344
- }
5345
- return withCwdMutex(`file-lock:${path}`, async () => {
5346
- const release = await lib.lock(path, {
5347
- // EC-1: companion lockfile, target path may not exist yet.
5348
- lockfilePath: `${path}.lock`,
5349
- realpath: false,
5350
- stale: 3e4,
5351
- retries: {
5352
- retries: 5,
5353
- factor: 1.5,
5354
- minTimeout: 100,
5355
- maxTimeout: 5e3
5356
- }
5357
- });
5358
- try {
5359
- return await fn();
5360
- } finally {
5361
- await release();
5362
- }
5363
- });
5364
- }
5895
+ init_file_lock();
5365
5896
  var THEOKIT_DIR_NAME = ".theokit";
5366
5897
  function getTheokitHome(cwd) {
5367
5898
  const override = process.env.THEOKIT_HOME?.trim();
@@ -5803,6 +6334,9 @@ var HISTOGRAM_NAMES = {
5803
6334
  /** M3 #66 — count of finishes where the provider omitted usage (silent undercount). */
5804
6335
  LLM_USAGE_MISSING: "theokit_llm_usage_missing"
5805
6336
  };
6337
+
6338
+ // src/internal/telemetry/tracer.ts
6339
+ init_security();
5806
6340
  function safeRequire(moduleName) {
5807
6341
  try {
5808
6342
  const r = module$1.createRequire((typeof document === 'undefined' ? require('u' + 'rl').pathToFileURL(__filename).href : (_documentCurrentScript && _documentCurrentScript.tagName.toUpperCase() === 'SCRIPT' && _documentCurrentScript.src || new URL('cron.cjs', document.baseURI).href)));
@@ -6282,415 +6816,163 @@ function spawnAndCollect(options) {
6282
6816
  if (options.stdin !== void 0 && child.stdin !== null) {
6283
6817
  child.stdin.end(options.stdin);
6284
6818
  }
6285
- });
6286
- }
6287
-
6288
- // src/internal/runtime/hooks/hooks-executor.ts
6289
- init_hooks_source();
6290
- var HooksExecutor = class {
6291
- constructor(cwd) {
6292
- this.cwd = cwd;
6293
- }
6294
- cwd;
6295
- config = {};
6296
- async initialize(settingSourcesIncludeProject) {
6297
- if (!settingSourcesIncludeProject) {
6298
- this.config = {};
6299
- return;
6300
- }
6301
- this.config = await loadHookConfig(this.cwd);
6302
- }
6303
- /** Fire every hook registered for `event` and aggregate the decisions. */
6304
- async run(payload) {
6305
- const commands = this.commandsFor(payload.event, payload.tool);
6306
- if (commands.length === 0) return { decisions: [], blocked: false };
6307
- const decisions = [];
6308
- for (const command of commands) {
6309
- const decision = await this.executeOne(command, payload);
6310
- decisions.push(decision);
6311
- if (decision.decision === "deny") {
6312
- const result = {
6313
- decisions,
6314
- blocked: true
6315
- };
6316
- if (decision.reason !== void 0) result.reason = decision.reason;
6317
- return result;
6318
- }
6319
- }
6320
- return { decisions, blocked: false };
6321
- }
6322
- commandsFor(event, tool) {
6323
- const list = this.config.hooks?.[event] ?? [];
6324
- if (tool === void 0) return list;
6325
- return list.filter((entry) => {
6326
- if (entry.matcher === void 0) return true;
6327
- try {
6328
- return new RegExp(entry.matcher).test(tool);
6329
- } catch {
6330
- return entry.matcher === tool;
6331
- }
6332
- });
6333
- }
6334
- async executeOne(command, payload) {
6335
- const timeoutMs = command.timeoutMs ?? 3e4;
6336
- const result = await spawnAndCollect({
6337
- command: "sh",
6338
- args: ["-c", command.command],
6339
- cwd: this.cwd,
6340
- timeoutMs,
6341
- stdin: JSON.stringify(payload)
6342
- });
6343
- if (result.timedOut) {
6344
- return { decision: "deny", reason: `Hook timed out after ${timeoutMs}ms` };
6345
- }
6346
- if (result.spawnError !== void 0) {
6347
- return { decision: "deny", reason: `Hook spawn failed: ${result.spawnError.message}` };
6348
- }
6349
- if (result.exitCode !== 0) {
6350
- return {
6351
- decision: "deny",
6352
- reason: result.stderr.trim().length > 0 ? result.stderr.trim() : `Hook exited with code ${result.exitCode}`
6353
- };
6354
- }
6355
- return parseDecisionFromStdout(result.stdout);
6356
- }
6357
- };
6358
- function parseDecisionFromStdout(stdout) {
6359
- const trimmed = stdout.trim();
6360
- if (trimmed.length === 0) return { decision: "allow" };
6361
- try {
6362
- const parsed = JSON.parse(trimmed);
6363
- if (parsed.decision === "deny" || parsed.decision === "feedback") {
6364
- const result = { decision: parsed.decision };
6365
- if (parsed.reason !== void 0) result.reason = parsed.reason;
6366
- if (parsed.feedback !== void 0) result.feedback = parsed.feedback;
6367
- return result;
6368
- }
6369
- if (parsed.decision === "allow") return { decision: "allow" };
6370
- } catch {
6371
- return { decision: "feedback", feedback: trimmed };
6372
- }
6373
- return { decision: "allow" };
6374
- }
6375
-
6376
- // src/internal/memory/storage/session-summary-writer.ts
6377
- init_atomic_write();
6378
- var MAX_TURN_CHARS = 2e3;
6379
- function sessionsDir(cwd) {
6380
- return path.join(memoryDir(cwd), "sessions");
6381
- }
6382
- function sessionSummaryPath(cwd, runId) {
6383
- return path.join(sessionsDir(cwd), `${safeFilenameForId(runId, { maxLen: 128 })}.md`);
6384
- }
6385
- function truncate(text) {
6386
- if (text.length <= MAX_TURN_CHARS) return text;
6387
- return `${text.slice(0, MAX_TURN_CHARS)}\u2026`;
6388
- }
6389
- async function writeSessionSummary(input) {
6390
- if (input.status !== "finished") return;
6391
- const path = sessionSummaryPath(input.cwd, input.runId);
6392
- await promises.mkdir(sessionsDir(input.cwd), { recursive: true });
6393
- const safeUser = redactSecrets(truncate(input.userText));
6394
- const safeAssistant = redactSecrets(truncate(input.assistantText));
6395
- const iso = new Date(input.at).toISOString();
6396
- const body = [
6397
- "---",
6398
- `runId: ${input.runId}`,
6399
- `agentId: ${input.agentId}`,
6400
- `at: ${iso}`,
6401
- `status: ${input.status}`,
6402
- "---",
6403
- "",
6404
- "## User",
6405
- "",
6406
- safeUser,
6407
- "",
6408
- "## Assistant",
6409
- "",
6410
- safeAssistant,
6411
- ""
6412
- ].join("\n");
6413
- await replaceFileAtomic(path, body);
6414
- }
6415
-
6416
- // src/internal/runtime/memory/memory-path-selector.ts
6417
- var PORT_MEMORY_PATH_ENV_VAR = "THEOKIT_PORT_MEMORY_PATH";
6418
- function shouldUsePortMemoryPath() {
6419
- const env = globalThis.process?.env;
6420
- if (env === void 0) return false;
6421
- const val = env[PORT_MEMORY_PATH_ENV_VAR];
6422
- return val === "1" || val === "true";
6423
- }
6424
- function resolveMemoryProviderForLoop(consumerSupplied, defaultAdapter, portPathEnabled) {
6425
- if (consumerSupplied !== void 0) return consumerSupplied;
6426
- if (portPathEnabled) return defaultAdapter;
6427
- return void 0;
6428
- }
6429
- function resolveMemoryToolsForLoop(legacyTools, portPathEnabled) {
6430
- if (portPathEnabled) return void 0;
6431
- return legacyTools;
6432
- }
6433
- function resolveActiveMemorySummaryForSend(legacySummary, portPathEnabled) {
6434
- if (portPathEnabled) return void 0;
6435
- return legacySummary;
6436
- }
6437
-
6438
- // src/internal/runtime/session/agent-session-store.ts
6439
- init_atomic_write();
6440
- var VALID_ROLES = /* @__PURE__ */ new Set([
6441
- "user",
6442
- "assistant",
6443
- "system",
6444
- "tool_call",
6445
- "tool_result"
6446
- ]);
6447
- function sessionFilePath(cwd, agentId) {
6448
- const safe2 = sanitizeIdentifier(agentId, { maxLen: 128 });
6449
- return safePathJoin(cwd, ".theokit", "agents", safe2, "messages.jsonl");
6450
- }
6451
- async function readJsonlLines(cwd, agentId) {
6452
- const path = sessionFilePath(cwd, agentId);
6453
- try {
6454
- const raw = await promises.readFile(path, "utf8");
6455
- return raw.split("\n").filter((line) => line.length > 0);
6456
- } catch {
6457
- return [];
6458
- }
6459
- }
6460
- function warnMalformed(agentId, line) {
6461
- process.stderr.write(
6462
- `[theokit-sdk] skipping malformed line in messages.jsonl (${agentId}): ${line.slice(0, 80)}...
6463
- `
6464
- );
6465
- }
6466
- function hydrateSessionLine(parsed) {
6467
- if (typeof parsed.text !== "string" || parsed.role === void 0) return void 0;
6468
- if (parsed.role === "user" || parsed.role === "assistant") {
6469
- return { role: parsed.role, text: parsed.text };
6470
- }
6471
- if (parsed.role === "tool_call" || parsed.role === "tool_result") {
6472
- const label = parsed.role === "tool_call" ? "tool call" : "tool result";
6473
- return { role: "assistant", text: `[${label}] ${parsed.text}` };
6474
- }
6475
- return void 0;
6476
- }
6477
- async function readSessionFile(cwd, agentId) {
6478
- const lines = await readJsonlLines(cwd, agentId);
6479
- const messages = [];
6480
- for (const line of lines) {
6481
- try {
6482
- const msg = hydrateSessionLine(JSON.parse(line));
6483
- if (msg !== void 0) messages.push(msg);
6484
- } catch {
6485
- warnMalformed(agentId, line);
6486
- }
6487
- }
6488
- return messages;
6489
- }
6490
- async function readAllPersistedMessages(cwd, agentId) {
6491
- const lines = await readJsonlLines(cwd, agentId);
6492
- const messages = [];
6493
- for (const line of lines) {
6494
- try {
6495
- const parsed = JSON.parse(line);
6496
- if (parsed.role !== void 0 && VALID_ROLES.has(parsed.role) && typeof parsed.text === "string") {
6497
- messages.push({
6498
- role: parsed.role,
6499
- text: parsed.text,
6500
- at: typeof parsed.at === "number" ? parsed.at : Date.now()
6501
- });
6502
- }
6503
- } catch {
6504
- warnMalformed(agentId, line);
6505
- }
6506
- }
6507
- return messages;
6508
- }
6509
- async function appendAnyPersistedMessage(cwd, agentId, record) {
6510
- await appendPersistedMessages(cwd, agentId, [record]);
6511
- }
6512
- async function appendPersistedMessages(cwd, agentId, records) {
6513
- if (records.length === 0) return;
6514
- const path$1 = sessionFilePath(cwd, agentId);
6515
- const payload = records.map((r) => `${redactSecrets(JSON.stringify(r))}
6516
- `).join("");
6517
- const dir = path.dirname(path$1);
6518
- let written = false;
6519
- const attempt = async () => {
6520
- await promises.mkdir(dir, { recursive: true });
6521
- await withFileLock(path$1, async () => {
6522
- await promises.appendFile(path$1, payload, "utf8");
6523
- written = true;
6524
- });
6525
- };
6526
- try {
6527
- await attempt();
6528
- } catch (cause) {
6529
- if (written || cause.code !== "ENOENT") throw cause;
6530
- await attempt();
6531
- }
6532
- }
6533
- async function rewriteLockedSession(path, transform) {
6534
- await withFileLock(path, async () => {
6535
- let raw;
6536
- try {
6537
- raw = await promises.readFile(path, "utf8");
6538
- } catch {
6539
- return;
6540
- }
6541
- const lines = raw.split("\n").filter((line) => line.length > 0);
6542
- const next = transform(lines);
6543
- if (next === void 0) return;
6544
- await replaceFileAtomic(path, next);
6545
- });
6546
- }
6547
- async function compactSessionFile(cwd, agentId, maxTurns) {
6548
- const path = sessionFilePath(cwd, agentId);
6549
- if (!fs.existsSync(path)) return;
6550
- await rewriteLockedSession(
6551
- path,
6552
- (lines) => lines.length <= maxTurns * 2 ? void 0 : `${lines.slice(-maxTurns).join("\n")}
6553
- `
6554
- );
6555
- }
6556
- async function truncateSessionTo(cwd, agentId, keepCount) {
6557
- const path = sessionFilePath(cwd, agentId);
6558
- if (!fs.existsSync(path)) return 0;
6559
- let kept = 0;
6560
- await rewriteLockedSession(path, (lines) => {
6561
- const keep = Math.max(0, Math.min(keepCount, lines.length));
6562
- kept = keep;
6563
- if (keep === lines.length) return void 0;
6564
- return keep === 0 ? "" : `${lines.slice(0, keep).join("\n")}
6565
- `;
6566
- });
6567
- return kept;
6568
- }
6569
-
6570
- // src/internal/persistence/pagination.ts
6571
- function paginate(items, opts) {
6572
- if (opts === void 0 || opts.offset === void 0 && opts.limit === void 0) return items;
6573
- const start = Math.max(0, opts.offset ?? 0);
6574
- const end = opts.limit === void 0 ? items.length : start + Math.max(0, opts.limit);
6575
- return items.slice(start, end);
6576
- }
6577
-
6578
- // src/internal/persistence/session-meta.ts
6579
- function applyMetaPatch(current, patch) {
6580
- const next = {};
6581
- if (current.title !== void 0) next.title = current.title;
6582
- if (current.tag !== void 0) next.tag = current.tag;
6583
- if (patch.title === null) delete next.title;
6584
- else if (patch.title !== void 0) next.title = patch.title;
6585
- if (patch.tag === null) delete next.tag;
6586
- else if (patch.tag !== void 0) next.tag = patch.tag;
6587
- return next;
6588
- }
6589
- function coerceSessionMeta(raw) {
6590
- if (typeof raw !== "object" || raw === null) return void 0;
6591
- const obj = raw;
6592
- const meta = {};
6593
- if (typeof obj.title === "string") meta.title = obj.title;
6594
- if (typeof obj.tag === "string") meta.tag = obj.tag;
6595
- return meta.title === void 0 && meta.tag === void 0 ? void 0 : meta;
6819
+ });
6596
6820
  }
6597
6821
 
6598
- // src/internal/persistence/conversation-storage-fs.ts
6599
- var FileSystemConversationStorage = class {
6600
- #root;
6601
- constructor(opts = {}) {
6602
- this.#root = opts.root ?? process.cwd();
6603
- }
6604
- /** Exposed for tests + diagnostics. The path is sanitized at use sites. */
6605
- get root() {
6606
- return this.#root;
6607
- }
6608
- async getMessages(conversationId, opts) {
6609
- const records = await readAllPersistedMessages(this.#root, conversationId);
6610
- const all = records.map(toStoredMessage);
6611
- return paginate(all, opts);
6612
- }
6613
- async appendMessage(conversationId, message) {
6614
- await appendAnyPersistedMessage(this.#root, conversationId, toRecord(message));
6615
- }
6616
- async appendMessages(conversationId, messages) {
6617
- await appendPersistedMessages(this.#root, conversationId, messages.map(toRecord));
6618
- }
6619
- async truncateConversation(conversationId, keepCount) {
6620
- return truncateSessionTo(this.#root, conversationId, keepCount);
6621
- }
6622
- async deleteConversation(conversationId) {
6623
- const safe2 = sanitizeIdentifier(conversationId, { maxLen: 128 });
6624
- const dirPath = safePathJoin(this.#root, ".theokit", "agents", safe2);
6625
- await promises.rm(dirPath, { recursive: true, force: true });
6626
- }
6627
- async deleteScope(prefix) {
6628
- const ids = await this.listConversationIds();
6629
- const matching = ids.filter((id) => id.startsWith(prefix));
6630
- for (const id of matching) await this.deleteConversation(id);
6631
- return matching.length;
6822
+ // src/internal/runtime/hooks/hooks-executor.ts
6823
+ init_hooks_source();
6824
+ var HooksExecutor = class {
6825
+ constructor(cwd) {
6826
+ this.cwd = cwd;
6632
6827
  }
6633
- async listConversationIds(opts = {}) {
6634
- const agentsRoot = safePathJoin(this.#root, ".theokit", "agents");
6635
- let entries;
6636
- try {
6637
- entries = await promises.readdir(agentsRoot);
6638
- } catch (cause) {
6639
- if (cause.code === "ENOENT") return [];
6640
- throw cause;
6828
+ cwd;
6829
+ config = {};
6830
+ async initialize(settingSourcesIncludeProject) {
6831
+ if (!settingSourcesIncludeProject) {
6832
+ this.config = {};
6833
+ return;
6641
6834
  }
6642
- if (opts.limit !== void 0) return entries.slice(0, opts.limit);
6643
- return entries;
6644
- }
6645
- async compact(conversationId, maxTurns) {
6646
- await compactSessionFile(this.#root, conversationId, maxTurns);
6647
- }
6648
- // SE4 — session metadata persisted as a per-conversation sidecar
6649
- // `<root>/.theokit/agents/<safeId>/session.json` (same sanitized perimeter as
6650
- // the transcript). Kept separate from messages.jsonl so a title/tag write does
6651
- // not rewrite the append-only log.
6652
- #metaPath(conversationId) {
6653
- const safe2 = sanitizeIdentifier(conversationId, { maxLen: 128 });
6654
- return safePathJoin(this.#root, ".theokit", "agents", safe2, "session.json");
6835
+ this.config = await loadHookConfig(this.cwd);
6655
6836
  }
6656
- async getSessionMeta(conversationId) {
6657
- try {
6658
- const raw = await promises.readFile(this.#metaPath(conversationId), "utf8");
6659
- return coerceSessionMeta(JSON.parse(raw));
6660
- } catch (cause) {
6661
- if (cause.code === "ENOENT") return void 0;
6662
- throw cause;
6837
+ /** Fire every hook registered for `event` and aggregate the decisions. */
6838
+ async run(payload) {
6839
+ const commands = this.commandsFor(payload.event, payload.tool);
6840
+ if (commands.length === 0) return { decisions: [], blocked: false };
6841
+ const decisions = [];
6842
+ for (const command of commands) {
6843
+ const decision = await this.executeOne(command, payload);
6844
+ decisions.push(decision);
6845
+ if (decision.decision === "deny") {
6846
+ const result = {
6847
+ decisions,
6848
+ blocked: true
6849
+ };
6850
+ if (decision.reason !== void 0) result.reason = decision.reason;
6851
+ return result;
6852
+ }
6663
6853
  }
6854
+ return { decisions, blocked: false };
6664
6855
  }
6665
- async setSessionMeta(conversationId, patch) {
6666
- const metaPath = this.#metaPath(conversationId);
6667
- await promises.mkdir(path.dirname(metaPath), { recursive: true });
6668
- await withFileLock(metaPath, async () => {
6669
- let current = {};
6856
+ commandsFor(event, tool) {
6857
+ const list = this.config.hooks?.[event] ?? [];
6858
+ if (tool === void 0) return list;
6859
+ return list.filter((entry) => {
6860
+ if (entry.matcher === void 0) return true;
6670
6861
  try {
6671
- current = coerceSessionMeta(JSON.parse(await promises.readFile(metaPath, "utf8"))) ?? {};
6672
- } catch (cause) {
6673
- if (cause.code !== "ENOENT") throw cause;
6862
+ return new RegExp(entry.matcher).test(tool);
6863
+ } catch {
6864
+ return entry.matcher === tool;
6674
6865
  }
6675
- const next = applyMetaPatch(current, patch);
6676
- await promises.writeFile(metaPath, redactSecrets(JSON.stringify(next)), "utf8");
6677
6866
  });
6678
6867
  }
6679
- async dispose() {
6868
+ async executeOne(command, payload) {
6869
+ const timeoutMs = command.timeoutMs ?? 3e4;
6870
+ const result = await spawnAndCollect({
6871
+ command: "sh",
6872
+ args: ["-c", command.command],
6873
+ cwd: this.cwd,
6874
+ timeoutMs,
6875
+ stdin: JSON.stringify(payload)
6876
+ });
6877
+ if (result.timedOut) {
6878
+ return { decision: "deny", reason: `Hook timed out after ${timeoutMs}ms` };
6879
+ }
6880
+ if (result.spawnError !== void 0) {
6881
+ return { decision: "deny", reason: `Hook spawn failed: ${result.spawnError.message}` };
6882
+ }
6883
+ if (result.exitCode !== 0) {
6884
+ return {
6885
+ decision: "deny",
6886
+ reason: result.stderr.trim().length > 0 ? result.stderr.trim() : `Hook exited with code ${result.exitCode}`
6887
+ };
6888
+ }
6889
+ return parseDecisionFromStdout(result.stdout);
6680
6890
  }
6681
6891
  };
6682
- function toStoredMessage(record) {
6683
- return {
6684
- role: record.role,
6685
- content: record.text,
6686
- at: record.at
6687
- };
6892
+ function parseDecisionFromStdout(stdout) {
6893
+ const trimmed = stdout.trim();
6894
+ if (trimmed.length === 0) return { decision: "allow" };
6895
+ try {
6896
+ const parsed = JSON.parse(trimmed);
6897
+ if (parsed.decision === "deny" || parsed.decision === "feedback") {
6898
+ const result = { decision: parsed.decision };
6899
+ if (parsed.reason !== void 0) result.reason = parsed.reason;
6900
+ if (parsed.feedback !== void 0) result.feedback = parsed.feedback;
6901
+ return result;
6902
+ }
6903
+ if (parsed.decision === "allow") return { decision: "allow" };
6904
+ } catch {
6905
+ return { decision: "feedback", feedback: trimmed };
6906
+ }
6907
+ return { decision: "allow" };
6688
6908
  }
6689
- function toRecord(message) {
6690
- return { role: message.role, text: message.content, at: message.at ?? Date.now() };
6909
+
6910
+ // src/internal/memory/storage/session-summary-writer.ts
6911
+ init_atomic_write();
6912
+ init_path_guard();
6913
+ var MAX_TURN_CHARS = 2e3;
6914
+ function sessionsDir(cwd) {
6915
+ return path.join(memoryDir(cwd), "sessions");
6916
+ }
6917
+ function sessionSummaryPath(cwd, runId) {
6918
+ return path.join(sessionsDir(cwd), `${safeFilenameForId(runId, { maxLen: 128 })}.md`);
6919
+ }
6920
+ function truncate(text) {
6921
+ if (text.length <= MAX_TURN_CHARS) return text;
6922
+ return `${text.slice(0, MAX_TURN_CHARS)}\u2026`;
6923
+ }
6924
+ async function writeSessionSummary(input) {
6925
+ if (input.status !== "finished") return;
6926
+ const path = sessionSummaryPath(input.cwd, input.runId);
6927
+ await promises.mkdir(sessionsDir(input.cwd), { recursive: true });
6928
+ const safeUser = redactSecrets(truncate(input.userText));
6929
+ const safeAssistant = redactSecrets(truncate(input.assistantText));
6930
+ const iso = new Date(input.at).toISOString();
6931
+ const body = [
6932
+ "---",
6933
+ `runId: ${input.runId}`,
6934
+ `agentId: ${input.agentId}`,
6935
+ `at: ${iso}`,
6936
+ `status: ${input.status}`,
6937
+ "---",
6938
+ "",
6939
+ "## User",
6940
+ "",
6941
+ safeUser,
6942
+ "",
6943
+ "## Assistant",
6944
+ "",
6945
+ safeAssistant,
6946
+ ""
6947
+ ].join("\n");
6948
+ await replaceFileAtomic(path, body);
6949
+ }
6950
+
6951
+ // src/internal/runtime/memory/memory-path-selector.ts
6952
+ var PORT_MEMORY_PATH_ENV_VAR = "THEOKIT_PORT_MEMORY_PATH";
6953
+ function shouldUsePortMemoryPath() {
6954
+ const env = globalThis.process?.env;
6955
+ if (env === void 0) return false;
6956
+ const val = env[PORT_MEMORY_PATH_ENV_VAR];
6957
+ return val === "1" || val === "true";
6958
+ }
6959
+ function resolveMemoryProviderForLoop(consumerSupplied, defaultAdapter, portPathEnabled) {
6960
+ if (consumerSupplied !== void 0) return consumerSupplied;
6961
+ if (portPathEnabled) return defaultAdapter;
6962
+ return void 0;
6963
+ }
6964
+ function resolveMemoryToolsForLoop(legacyTools, portPathEnabled) {
6965
+ if (portPathEnabled) return void 0;
6966
+ return legacyTools;
6967
+ }
6968
+ function resolveActiveMemorySummaryForSend(legacySummary, portPathEnabled) {
6969
+ if (portPathEnabled) return void 0;
6970
+ return legacySummary;
6691
6971
  }
6692
6972
 
6693
6973
  // src/internal/runtime/session/agent-session.ts
6974
+ init_conversation_storage_fs();
6975
+ init_agent_session_store();
6694
6976
  var DEFAULT_MAX_TURNS = 200;
6695
6977
  var COMPACTION_CHECK_INTERVAL = 50;
6696
6978
  var sessions = /* @__PURE__ */ new Map();
@@ -7130,6 +7412,7 @@ function parseFrontmatterFields(frontmatter) {
7130
7412
 
7131
7413
  // src/internal/runtime/skills/discover-skills.ts
7132
7414
  init_errors();
7415
+ init_path_guard();
7133
7416
 
7134
7417
  // src/internal/runtime/skills/skill-frontmatter.ts
7135
7418
  init_errors();
@@ -8450,6 +8733,7 @@ function tokenizeContent(content) {
8450
8733
  // src/internal/runtime/plugins/plugins-manager.ts
8451
8734
  init_errors();
8452
8735
  init_markdown_config_loader();
8736
+ init_path_guard();
8453
8737
  init_hooks_source();
8454
8738
  var PluginFrontmatterSchema = zod.z.object({
8455
8739
  /** Plugin identifier; defaults to folder name if omitted. */
@@ -10909,6 +11193,7 @@ function registerBuiltins() {
10909
11193
  init_errors();
10910
11194
 
10911
11195
  // src/internal/error-mappers/shared.ts
11196
+ init_security();
10912
11197
  var RAW_MAX_BYTES = 2048;
10913
11198
  function parseRetryAfter(headers) {
10914
11199
  if (headers === void 0) return void 0;
@@ -13040,6 +13325,7 @@ function selectTransport(profile, apiKey) {
13040
13325
 
13041
13326
  // src/internal/mcp/client.ts
13042
13327
  init_errors();
13328
+ init_path_guard();
13043
13329
  function createMcpClient(name, config, fetchImpl = fetch) {
13044
13330
  if (isStdio(config)) return new StdioMcpClient(name, config);
13045
13331
  return new HttpMcpClient(name, config, fetchImpl);
@@ -13749,6 +14035,9 @@ async function readProjectMcpServers(cwd) {
13749
14035
  }
13750
14036
  }
13751
14037
 
14038
+ // src/internal/runtime/local-agent/local-agent.ts
14039
+ init_local_agent_goal_extensions();
14040
+
13752
14041
  // src/internal/runtime/local-agent/local-agent-invalidate.ts
13753
14042
  async function applyDeferredInvalidation(agentId, pending, refresh) {
13754
14043
  process.stderr.write(
@@ -16079,16 +16368,49 @@ async function localAgentUsePersonality(args) {
16079
16368
  init_local_agent_plugins();
16080
16369
 
16081
16370
  // src/internal/runtime/local-agent/local-agent-runtime-extensions.ts
16082
- function localAgentRunUntil(agent, goal, options) {
16371
+ function pausedReason(kind, threadId) {
16372
+ switch (kind) {
16373
+ case "none":
16374
+ return `no durable objective set for thread "${threadId}" \u2014 call setObjective() first`;
16375
+ case "inert":
16376
+ return `durable objective for thread "${threadId}" is inert \u2014 no judge resolved (set a judge to activate)`;
16377
+ case "exhausted":
16378
+ return `durable objective for thread "${threadId}" exhausted its run budget \u2014 raise maxRuns to resume`;
16379
+ }
16380
+ }
16381
+ function localAgentRunUntil(agent, goal, options, durable) {
16083
16382
  async function* wrap() {
16084
16383
  const { runUntilImpl: runUntilImpl2 } = await Promise.resolve().then(() => (init_run_until(), run_until_exports));
16085
- const { judgeCallImpl: judgeCallImpl2 } = await Promise.resolve().then(() => (init_judge_call(), judge_call_exports));
16086
- const { getAgentFacade: getAgentFacade2 } = await Promise.resolve().then(() => (init_agent_factory_registry(), agent_factory_registry_exports));
16087
- const create = getAgentFacade2().create;
16088
- const deps = {
16089
- judge: async (ctx, opts) => judgeCallImpl2(ctx, opts, { create })
16384
+ const buildDeps = async () => {
16385
+ const { judgeCallImpl: judgeCallImpl2 } = await Promise.resolve().then(() => (init_judge_call(), judge_call_exports));
16386
+ const { getAgentFacade: getAgentFacade2 } = await Promise.resolve().then(() => (init_agent_factory_registry(), agent_factory_registry_exports));
16387
+ const create = getAgentFacade2().create;
16388
+ return {
16389
+ judge: (ctx, opts) => judgeCallImpl2(ctx, opts, { create })
16390
+ };
16090
16391
  };
16091
- return yield* runUntilImpl2(agent, goal, options, deps);
16392
+ if (goal !== void 0) {
16393
+ return yield* runUntilImpl2(agent, goal, options, await buildDeps());
16394
+ }
16395
+ const threadId = options?.threadId;
16396
+ if (durable === void 0 || threadId === void 0) {
16397
+ const reason = "runUntil() called with no goal and no threadId \u2014 nothing to resolve a durable objective from";
16398
+ yield { type: "status_change", status: "paused", reason };
16399
+ return { status: "paused", turnsUsed: 0, finalResponse: void 0 };
16400
+ }
16401
+ const { resolveDurableRun: resolveDurableRun2, persistDurableProgress: persistDurableProgress2 } = await Promise.resolve().then(() => (init_local_agent_goal_extensions(), local_agent_goal_extensions_exports));
16402
+ const resolved = await resolveDurableRun2(durable.handle, durable.goalConfig, threadId, options);
16403
+ if (resolved.kind !== "run") {
16404
+ yield {
16405
+ type: "status_change",
16406
+ status: "paused",
16407
+ reason: pausedReason(resolved.kind, threadId)
16408
+ };
16409
+ return { status: "paused", turnsUsed: 0, finalResponse: void 0 };
16410
+ }
16411
+ const result = yield* runUntilImpl2(agent, resolved.goal, resolved.options, await buildDeps());
16412
+ await persistDurableProgress2(durable.handle, threadId, result);
16413
+ return result;
16092
16414
  }
16093
16415
  return wrap();
16094
16416
  }
@@ -16154,6 +16476,44 @@ function ponyfillAny(signals) {
16154
16476
  return ctrl.signal;
16155
16477
  }
16156
16478
 
16479
+ // src/internal/runtime/lifecycle/wrap-completion-check-run.ts
16480
+ function wrapRunWithCompletionCheck(args) {
16481
+ const check = args.completionCheck;
16482
+ if (check === void 0) return args.run;
16483
+ const compute = async () => {
16484
+ const result = await args.run.wait();
16485
+ if (result.status !== "finished" || result.result === void 0) return result;
16486
+ const judgeOpts = {};
16487
+ if (check.judgeModel !== void 0) judgeOpts.judgeModel = check.judgeModel;
16488
+ if (check.apiKey !== void 0) judgeOpts.apiKey = check.apiKey;
16489
+ const verdict = await args.deps.judge(
16490
+ { goal: check.criteria, lastResponse: result.result },
16491
+ judgeOpts
16492
+ );
16493
+ const complete = !verdict.parseFailed && verdict.verdict === "done";
16494
+ emitRunEvent(args.onRunEvent, {
16495
+ type: "completion_check",
16496
+ complete,
16497
+ reason: verdict.reason
16498
+ });
16499
+ return {
16500
+ ...result,
16501
+ completionCheck: { complete, reason: verdict.reason, parseFailed: verdict.parseFailed }
16502
+ };
16503
+ };
16504
+ let judged;
16505
+ const wrappedWait = () => {
16506
+ judged ??= compute();
16507
+ return judged;
16508
+ };
16509
+ return new Proxy(args.run, {
16510
+ get(target, prop, receiver) {
16511
+ if (prop === "wait") return wrappedWait;
16512
+ return Reflect.get(target, prop, receiver);
16513
+ }
16514
+ });
16515
+ }
16516
+
16157
16517
  // src/internal/runtime/processors/run-processors.ts
16158
16518
  var ProcessorAbort = class {
16159
16519
  constructor(processorId, reason) {
@@ -16293,6 +16653,9 @@ function wrapRunWithOutputProcessors(args) {
16293
16653
  });
16294
16654
  }
16295
16655
 
16656
+ // src/internal/runtime/local-agent/local-agent-send.ts
16657
+ init_local_agent_goal_extensions();
16658
+
16296
16659
  // src/internal/runtime/local-agent/local-agent-memory-hooks.ts
16297
16660
  var DEFAULT_MAX_RECALL_BYTES = 16e3;
16298
16661
  async function applyPreUserSendHook(args) {
@@ -16408,6 +16771,11 @@ async function executeSendLocked(inputs, message, options) {
16408
16771
  memoryFacts,
16409
16772
  activeMemorySummary
16410
16773
  );
16774
+ const projectedSystemPrompt = await projectCurrentObjective(
16775
+ inputs.storageHandle,
16776
+ options.objectiveThreadId,
16777
+ assembledSystemPrompt
16778
+ );
16411
16779
  const composedOptions = {
16412
16780
  ...options,
16413
16781
  signal: anySignal([options.signal, inputs.lifecycleAbortController.signal])
@@ -16415,7 +16783,7 @@ async function executeSendLocked(inputs, message, options) {
16415
16783
  const run = await inputs.dispatchRun(
16416
16784
  adaptedMessage,
16417
16785
  composedOptions,
16418
- assembledSystemPrompt,
16786
+ projectedSystemPrompt,
16419
16787
  memoryFacts,
16420
16788
  priorMessages,
16421
16789
  memoryTools,
@@ -16428,18 +16796,43 @@ async function executeSendLocked(inputs, message, options) {
16428
16796
  agentId: inputs.agentId,
16429
16797
  onRunEvent: options.onRunEvent
16430
16798
  }) : run;
16431
- return wrapRunWithPostReplyHook({
16799
+ const hookedRun = wrapRunWithPostReplyHook({
16432
16800
  pluginManager: inputs.pluginManagerCode,
16433
16801
  agentId: inputs.agentId,
16434
16802
  options: inputs.options,
16435
16803
  run: processedRun,
16436
16804
  userText
16437
16805
  });
16806
+ return wrapRunWithCompletionCheck({
16807
+ run: hookedRun,
16808
+ completionCheck: options.completionCheck,
16809
+ onRunEvent: options.onRunEvent,
16810
+ deps: buildCompletionCheckDeps()
16811
+ });
16812
+ }
16813
+ function buildCompletionCheckDeps() {
16814
+ return {
16815
+ judge: async (ctx, opts) => {
16816
+ const { judgeCallImpl: judgeCallImpl2 } = await Promise.resolve().then(() => (init_judge_call(), judge_call_exports));
16817
+ const { getAgentFacade: getAgentFacade2 } = await Promise.resolve().then(() => (init_agent_factory_registry(), agent_factory_registry_exports));
16818
+ return judgeCallImpl2(ctx, opts, { create: getAgentFacade2().create });
16819
+ }
16820
+ };
16438
16821
  }
16439
16822
  function readMemoryForSend(workspaceCwd, memoryConfig) {
16440
16823
  if (memoryConfig?.enabled !== true) return Promise.resolve([]);
16441
16824
  return safeCall(() => readMemoryFacts(workspaceCwd, memoryConfig), [], "memory read");
16442
16825
  }
16826
+ async function projectCurrentObjective(storageHandle, objectiveThreadId, assembled) {
16827
+ if (objectiveThreadId === void 0) return assembled;
16828
+ const objective = await safeCall(
16829
+ () => resolveCurrentObjectiveText(storageHandle, objectiveThreadId),
16830
+ void 0,
16831
+ "objective projection"
16832
+ );
16833
+ if (objective === void 0) return assembled;
16834
+ return formatObjectiveProjection(objective, assembled);
16835
+ }
16443
16836
 
16444
16837
  // src/internal/runtime/local-agent/local-agent-task-wrap.ts
16445
16838
  init_registry();
@@ -16802,20 +17195,33 @@ var LocalAgent = class {
16802
17195
  ...opts !== void 0 ? { opts } : {}
16803
17196
  });
16804
17197
  }
17198
+ // biome-ignore format: G8 budget — artifact stubs (local agents have no artifacts); kept 1-line each.
16805
17199
  listArtifacts() {
16806
17200
  return Promise.resolve([]);
16807
17201
  }
17202
+ // biome-ignore format: G8 budget — see listArtifacts.
16808
17203
  downloadArtifact(_path) {
16809
- return Promise.reject(
16810
- new UnsupportedRunOperationError(
16811
- "Artifacts are not supported for local agents",
16812
- "downloadArtifact"
16813
- )
16814
- );
17204
+ return Promise.reject(new UnsupportedRunOperationError("Artifacts are not supported for local agents", "downloadArtifact"));
16815
17205
  }
16816
17206
  // biome-ignore format: G8 budget — both methods delegate to `local-agent-runtime-extensions.ts`; signatures kept as 1-line each.
16817
17207
  runUntil(goal, options) {
16818
- return localAgentRunUntil(this, goal, options);
17208
+ return localAgentRunUntil(this, goal, options, { handle: this.storageHandle(), goalConfig: this.options.goal });
17209
+ }
17210
+ // biome-ignore format: SE33 G8 budget — objective methods delegate to `local-agent-goal-extensions.ts`.
17211
+ setObjective(objective, opts) {
17212
+ return localAgentSetObjective(this.storageHandle(), objective, opts);
17213
+ }
17214
+ // biome-ignore format: SE33 G8 budget — see setObjective above.
17215
+ getObjective(opts) {
17216
+ return localAgentGetObjective(this.storageHandle(), opts);
17217
+ }
17218
+ // biome-ignore format: SE33 G8 budget — see setObjective above.
17219
+ updateObjectiveOptions(opts) {
17220
+ return localAgentUpdateObjectiveOptions(this.storageHandle(), opts);
17221
+ }
17222
+ // biome-ignore format: SE33 G8 budget — see setObjective above.
17223
+ clearObjective(opts) {
17224
+ return localAgentClearObjective(this.storageHandle(), opts);
16819
17225
  }
16820
17226
  // biome-ignore format: G8 budget — see runUntil comment above.
16821
17227
  fork(options) {
@@ -17363,15 +17769,31 @@ setAgentFacade({
17363
17769
  // src/cron.ts
17364
17770
  init_errors();
17365
17771
 
17772
+ // src/internal/cron/fire-handler.ts
17773
+ init_registry();
17774
+
17366
17775
  // src/internal/cron/run-job.ts
17367
17776
  init_errors();
17368
17777
  init_agent_factory_registry();
17369
17778
  async function runCronJob(job) {
17370
- if (job.agent !== void 0) return runWithEphemeralAgent(job.agent, job.message);
17371
- if (job.agentId !== void 0) return runWithExistingAgent(job.agentId, job.message);
17372
- throw new ConfigurationError(`Cron job ${job.id} has neither agent nor agentId \u2014 cannot run.`, {
17373
- code: "cron_no_target"
17374
- });
17779
+ if (job.workflow !== void 0) return job.workflow.run(job.inputData);
17780
+ if (job.agent !== void 0) return runWithEphemeralAgent(job.agent, requireMessage(job));
17781
+ if (job.agentId !== void 0) return runWithExistingAgent(job.agentId, requireMessage(job));
17782
+ throw new ConfigurationError(
17783
+ `Cron job ${job.id} has no target (agent, agentId, or workflow) \u2014 cannot run.`,
17784
+ { code: "cron_no_target" }
17785
+ );
17786
+ }
17787
+ function isAgentRun(outcome) {
17788
+ return typeof outcome.wait === "function";
17789
+ }
17790
+ function requireMessage(job) {
17791
+ if (job.message === void 0) {
17792
+ throw new ConfigurationError(`Cron job ${job.id} is an agent target but has no message.`, {
17793
+ code: "cron_missing_message"
17794
+ });
17795
+ }
17796
+ return job.message;
17375
17797
  }
17376
17798
  async function runWithExistingAgent(agentId, message) {
17377
17799
  const info = await getAgentFacade().get(agentId).catch(() => void 0);
@@ -17389,6 +17811,37 @@ async function runWithEphemeralAgent(baseOptions, message) {
17389
17811
  return agent.send(message);
17390
17812
  }
17391
17813
 
17814
+ // src/internal/cron/fire-handler.ts
17815
+ async function fireCronJobAsTask(job) {
17816
+ const fireTs = Date.now();
17817
+ const taskId = `cron-${job.id}-${fireTs}`;
17818
+ try {
17819
+ await submit({
17820
+ kind: "cron",
17821
+ id: taskId,
17822
+ allowReservedPrefix: true,
17823
+ meta: { jobId: job.id, jobName: job.name, schedule: job.cron, firedAt: fireTs },
17824
+ work: async (ctx) => {
17825
+ const outcome = await runCronJob(job);
17826
+ if (isAgentRun(outcome)) {
17827
+ ctx.signal.addEventListener("abort", () => void outcome.cancel().catch(() => {
17828
+ }), {
17829
+ once: true
17830
+ });
17831
+ const result = await outcome.wait();
17832
+ ctx.emit({ status: result.status, runId: outcome.id });
17833
+ return { status: result.status, runId: outcome.id };
17834
+ }
17835
+ ctx.emit({ status: outcome.status, runId: outcome.id });
17836
+ return { status: outcome.status, runId: outcome.id };
17837
+ }
17838
+ });
17839
+ } catch {
17840
+ const outcome = await runCronJob(job);
17841
+ if (isAgentRun(outcome)) await outcome.wait();
17842
+ }
17843
+ }
17844
+
17392
17845
  // src/internal/cron/store.ts
17393
17846
  var jobs = /* @__PURE__ */ new Map();
17394
17847
  function listJobs() {
@@ -17579,7 +18032,6 @@ function estimateNextRunAt(_cron, _timezone) {
17579
18032
  }
17580
18033
 
17581
18034
  // src/cron.ts
17582
- init_registry();
17583
18035
  var Cron = class {
17584
18036
  constructor() {
17585
18037
  }
@@ -17646,7 +18098,8 @@ var Cron = class {
17646
18098
  return updateJobStatus(jobId, false);
17647
18099
  }
17648
18100
  /**
17649
- * Manually trigger a cron job off-schedule. Returns the resulting `Run`.
18101
+ * Manually trigger a cron job off-schedule. Returns the resulting `Run`
18102
+ * (agent target) or `WorkflowRun` (workflow target — SE35).
17650
18103
  *
17651
18104
  * @public
17652
18105
  */
@@ -17663,30 +18116,7 @@ var Cron = class {
17663
18116
  * @public
17664
18117
  */
17665
18118
  static start(options = {}) {
17666
- setCronFireHandler(async (job) => {
17667
- const fireTs = Date.now();
17668
- const taskId = `cron-${job.id}-${fireTs}`;
17669
- try {
17670
- await submit({
17671
- kind: "cron",
17672
- id: taskId,
17673
- allowReservedPrefix: true,
17674
- meta: { jobId: job.id, jobName: job.name, schedule: job.cron, firedAt: fireTs },
17675
- work: async (ctx) => {
17676
- const run = await runCronJob(job);
17677
- ctx.signal.addEventListener("abort", () => void run.cancel().catch(() => {
17678
- }), {
17679
- once: true
17680
- });
17681
- const result = await run.wait();
17682
- ctx.emit({ status: result.status, runId: run.id });
17683
- return { status: result.status, runId: run.id };
17684
- }
17685
- });
17686
- } catch {
17687
- await runCronJob(job).then((run) => run.wait());
17688
- }
17689
- });
18119
+ setCronFireHandler(fireCronJobAsTask);
17690
18120
  startScheduler(options.cwd);
17691
18121
  return Promise.resolve();
17692
18122
  }
@@ -17710,17 +18140,7 @@ var Cron = class {
17710
18140
  }
17711
18141
  };
17712
18142
  async function createCronJob(options) {
17713
- if (options.agent !== void 0 && options.agentId !== void 0) {
17714
- throw new ConfigurationError(
17715
- "agent and agentId are mutually exclusive \u2014 pass either agent (ephemeral) or agentId (reuse).",
17716
- { code: "cron_agent_exclusive" }
17717
- );
17718
- }
17719
- if (options.agent === void 0 && options.agentId === void 0) {
17720
- throw new ConfigurationError("Cron job requires either agent or agentId", {
17721
- code: "cron_missing_agent"
17722
- });
17723
- }
18143
+ validateCronTarget(options);
17724
18144
  validateCronExpression(options.cron);
17725
18145
  const timezone = options.timezone ?? "UTC";
17726
18146
  validateTimezone(timezone);
@@ -17731,20 +18151,51 @@ async function createCronJob(options) {
17731
18151
  id: generateCronId(),
17732
18152
  cron: options.cron,
17733
18153
  timezone,
17734
- message: options.message,
17735
18154
  enabled: options.enabled ?? true,
17736
18155
  status: options.enabled === false ? "paused" : "scheduled",
17737
18156
  runtime,
17738
18157
  createdAt: now,
17739
18158
  nextRunAt: estimateNextRunAt(options.cron),
17740
18159
  ...options.name !== void 0 ? { name: options.name } : {},
18160
+ ...options.message !== void 0 ? { message: options.message } : {},
17741
18161
  ...options.agent !== void 0 ? { agent: options.agent } : {},
17742
- ...options.agentId !== void 0 ? { agentId: options.agentId } : {}
18162
+ ...options.agentId !== void 0 ? { agentId: options.agentId } : {},
18163
+ ...options.workflow !== void 0 ? { workflow: options.workflow } : {},
18164
+ ...options.inputData !== void 0 ? { inputData: options.inputData } : {}
17743
18165
  };
17744
18166
  upsertJob(job);
17745
18167
  return job;
17746
18168
  }
18169
+ function validateCronTarget(options) {
18170
+ const targets = [options.agent, options.agentId, options.workflow].filter(
18171
+ (t) => t !== void 0
18172
+ ).length;
18173
+ if (targets > 1) {
18174
+ throw new ConfigurationError(
18175
+ "agent, agentId, and workflow are mutually exclusive \u2014 pass exactly one target.",
18176
+ { code: "cron_ambiguous_target" }
18177
+ );
18178
+ }
18179
+ if (targets === 0) {
18180
+ throw new ConfigurationError(
18181
+ "Cron job requires exactly one target: agent, agentId, or workflow.",
18182
+ { code: "cron_no_target" }
18183
+ );
18184
+ }
18185
+ if (options.workflow !== void 0 && options.message !== void 0) {
18186
+ throw new ConfigurationError(
18187
+ "A workflow cron target takes inputData, not a message \u2014 remove `message`.",
18188
+ { code: "cron_workflow_message" }
18189
+ );
18190
+ }
18191
+ if (options.workflow === void 0 && options.message === void 0) {
18192
+ throw new ConfigurationError("An agent cron target requires a message.", {
18193
+ code: "cron_missing_message"
18194
+ });
18195
+ }
18196
+ }
17747
18197
  function detectRuntime(options) {
18198
+ if (options.workflow !== void 0) return "local";
17748
18199
  if (options.agentId !== void 0) {
17749
18200
  return options.agentId.startsWith("bc-") ? "cloud" : "local";
17750
18201
  }