@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/index.cjs CHANGED
@@ -916,7 +916,7 @@ function safeFilenameForId(id, options) {
916
916
  code: "invalid_filename_id"
917
917
  });
918
918
  }
919
- const maxLen = options?.maxLen;
919
+ const maxLen = options?.maxLen ?? 128;
920
920
  const lower = id.toLowerCase();
921
921
  if (lower.length <= maxLen && IDENTIFIER_PATTERN.test(lower)) {
922
922
  return lower;
@@ -1113,6 +1113,72 @@ var init_markdown_store = __esm({
1113
1113
  }
1114
1114
  });
1115
1115
 
1116
+ // src/internal/persistence/file-lock.ts
1117
+ async function getProperLockfile() {
1118
+ if (cached !== void 0) return cached;
1119
+ try {
1120
+ const mod = await import('proper-lockfile');
1121
+ if (!validateLockModule(mod)) {
1122
+ if (!warnedStructural) {
1123
+ warnedStructural = true;
1124
+ process.stderr.write(
1125
+ "[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"
1126
+ );
1127
+ }
1128
+ cached = null;
1129
+ return cached;
1130
+ }
1131
+ cached = mod;
1132
+ } catch {
1133
+ cached = null;
1134
+ }
1135
+ return cached;
1136
+ }
1137
+ function validateLockModule(mod) {
1138
+ if (mod === null || mod === void 0 || typeof mod !== "object") return false;
1139
+ const m = mod;
1140
+ return typeof m.lock === "function" && typeof m.unlock === "function";
1141
+ }
1142
+ async function withFileLock(path, fn, options) {
1143
+ const lib = await getProperLockfile();
1144
+ if (lib === null) {
1145
+ if (!warnedMissing) {
1146
+ warnedMissing = true;
1147
+ process.stderr.write(
1148
+ "[theokit-sdk] proper-lockfile not installed; cross-process file lock unavailable. Install with: pnpm add proper-lockfile\n"
1149
+ );
1150
+ }
1151
+ return withCwdMutex(`file-lock:${path}`, fn);
1152
+ }
1153
+ return withCwdMutex(`file-lock:${path}`, async () => {
1154
+ const release = await lib.lock(path, {
1155
+ // EC-1: companion lockfile, target path may not exist yet.
1156
+ lockfilePath: `${path}.lock`,
1157
+ realpath: false,
1158
+ stale: 3e4,
1159
+ retries: {
1160
+ retries: 5,
1161
+ factor: 1.5,
1162
+ minTimeout: 100,
1163
+ maxTimeout: 5e3
1164
+ }
1165
+ });
1166
+ try {
1167
+ return await fn();
1168
+ } finally {
1169
+ await release();
1170
+ }
1171
+ });
1172
+ }
1173
+ var cached, warnedMissing, warnedStructural;
1174
+ var init_file_lock = __esm({
1175
+ "src/internal/persistence/file-lock.ts"() {
1176
+ init_cwd_mutex();
1177
+ warnedMissing = false;
1178
+ warnedStructural = false;
1179
+ }
1180
+ });
1181
+
1116
1182
  // src/internal/runtime/context/yaml-frontmatter.ts
1117
1183
  function parseSimpleYaml(text) {
1118
1184
  const fields = {};
@@ -1367,94 +1433,459 @@ var init_session_summary_writer = __esm({
1367
1433
  MAX_TURN_CHARS = 2e3;
1368
1434
  }
1369
1435
  });
1370
- async function withToolWhitelist(whitelist, fn) {
1371
- return toolWhitelistStore.run(whitelist, fn);
1372
- }
1373
- function currentToolWhitelist() {
1374
- return toolWhitelistStore.getStore();
1436
+ function sessionFilePath(cwd, agentId) {
1437
+ const safe2 = sanitizeIdentifier(agentId, { maxLen: 128 });
1438
+ return safePathJoin(cwd, ".theokit", "agents", safe2, "messages.jsonl");
1375
1439
  }
1376
- function checkToolWhitelist(toolName) {
1377
- const whitelist = currentToolWhitelist();
1378
- if (whitelist === void 0) return { allowed: true };
1379
- if (!whitelist.has(toolName)) {
1380
- return {
1381
- allowed: false,
1382
- reason: `Tool "${toolName}" not available in this fork context`
1383
- };
1440
+ async function readJsonlLines(cwd, agentId) {
1441
+ const path = sessionFilePath(cwd, agentId);
1442
+ try {
1443
+ const raw = await promises.readFile(path, "utf8");
1444
+ return raw.split("\n").filter((line) => line.length > 0);
1445
+ } catch {
1446
+ return [];
1384
1447
  }
1385
- return { allowed: true };
1386
1448
  }
1387
- var toolWhitelistStore;
1388
- var init_async_local_storage = __esm({
1389
- "src/internal/runtime/concurrency/async-local-storage.ts"() {
1390
- toolWhitelistStore = new async_hooks.AsyncLocalStorage();
1449
+ function warnMalformed(agentId, line) {
1450
+ process.stderr.write(
1451
+ `[theokit-sdk] skipping malformed line in messages.jsonl (${agentId}): ${line.slice(0, 80)}...
1452
+ `
1453
+ );
1454
+ }
1455
+ function hydrateSessionLine(parsed) {
1456
+ if (typeof parsed.text !== "string" || parsed.role === void 0) return void 0;
1457
+ if (parsed.role === "user" || parsed.role === "assistant") {
1458
+ return { role: parsed.role, text: parsed.text };
1391
1459
  }
1392
- });
1393
-
1394
- // src/internal/runtime/concurrency/async-semaphore.ts
1395
- function createSemaphore(permits) {
1396
- if (!Number.isInteger(permits) || permits < 1) {
1397
- throw new exports.ConfigurationError(
1398
- `async-semaphore: permits must be a positive integer, got ${permits}`,
1399
- { code: "invalid_concurrency" }
1400
- );
1460
+ if (parsed.role === "tool_call" || parsed.role === "tool_result") {
1461
+ const label = parsed.role === "tool_call" ? "tool call" : "tool result";
1462
+ return { role: "assistant", text: `[${label}] ${parsed.text}` };
1401
1463
  }
1402
- let active = 0;
1403
- const queue = [];
1404
- function tryGrant() {
1405
- if (active < permits && queue.length > 0) {
1406
- const resolve3 = queue.shift();
1407
- if (resolve3 !== void 0) {
1408
- active += 1;
1409
- resolve3();
1410
- }
1464
+ return void 0;
1465
+ }
1466
+ async function readSessionFile(cwd, agentId) {
1467
+ const lines = await readJsonlLines(cwd, agentId);
1468
+ const messages = [];
1469
+ for (const line of lines) {
1470
+ try {
1471
+ const msg = hydrateSessionLine(JSON.parse(line));
1472
+ if (msg !== void 0) messages.push(msg);
1473
+ } catch {
1474
+ warnMalformed(agentId, line);
1411
1475
  }
1412
1476
  }
1413
- return {
1414
- inFlight: () => active,
1415
- pending: () => queue.length + active,
1416
- async acquire() {
1417
- await new Promise((resolve3) => {
1418
- queue.push(resolve3);
1419
- tryGrant();
1420
- });
1421
- let released = false;
1422
- return () => {
1423
- if (released) return;
1424
- released = true;
1425
- active -= 1;
1426
- tryGrant();
1427
- };
1477
+ return messages;
1478
+ }
1479
+ async function readAllPersistedMessages(cwd, agentId) {
1480
+ const lines = await readJsonlLines(cwd, agentId);
1481
+ const messages = [];
1482
+ for (const line of lines) {
1483
+ try {
1484
+ const parsed = JSON.parse(line);
1485
+ if (parsed.role !== void 0 && VALID_ROLES.has(parsed.role) && typeof parsed.text === "string") {
1486
+ messages.push({
1487
+ role: parsed.role,
1488
+ text: parsed.text,
1489
+ at: typeof parsed.at === "number" ? parsed.at : Date.now()
1490
+ });
1491
+ }
1492
+ } catch {
1493
+ warnMalformed(agentId, line);
1428
1494
  }
1495
+ }
1496
+ return messages;
1497
+ }
1498
+ async function appendAnyPersistedMessage(cwd, agentId, record) {
1499
+ await appendPersistedMessages(cwd, agentId, [record]);
1500
+ }
1501
+ async function appendPersistedMessages(cwd, agentId, records) {
1502
+ if (records.length === 0) return;
1503
+ const path$1 = sessionFilePath(cwd, agentId);
1504
+ const payload = records.map((r) => `${redactSecrets(JSON.stringify(r))}
1505
+ `).join("");
1506
+ const dir = path.dirname(path$1);
1507
+ let written = false;
1508
+ const attempt = async () => {
1509
+ await promises.mkdir(dir, { recursive: true });
1510
+ await withFileLock(path$1, async () => {
1511
+ await promises.appendFile(path$1, payload, "utf8");
1512
+ written = true;
1513
+ });
1429
1514
  };
1515
+ try {
1516
+ await attempt();
1517
+ } catch (cause) {
1518
+ if (written || cause.code !== "ENOENT") throw cause;
1519
+ await attempt();
1520
+ }
1430
1521
  }
1431
- var init_async_semaphore = __esm({
1432
- "src/internal/runtime/concurrency/async-semaphore.ts"() {
1433
- init_errors();
1522
+ async function rewriteLockedSession(path, transform) {
1523
+ await withFileLock(path, async () => {
1524
+ let raw;
1525
+ try {
1526
+ raw = await promises.readFile(path, "utf8");
1527
+ } catch {
1528
+ return;
1529
+ }
1530
+ const lines = raw.split("\n").filter((line) => line.length > 0);
1531
+ const next = transform(lines);
1532
+ if (next === void 0) return;
1533
+ await replaceFileAtomic(path, next);
1534
+ });
1535
+ }
1536
+ async function compactSessionFile(cwd, agentId, maxTurns) {
1537
+ const path = sessionFilePath(cwd, agentId);
1538
+ if (!fs.existsSync(path)) return;
1539
+ await rewriteLockedSession(
1540
+ path,
1541
+ (lines) => lines.length <= maxTurns * 2 ? void 0 : `${lines.slice(-maxTurns).join("\n")}
1542
+ `
1543
+ );
1544
+ }
1545
+ async function truncateSessionTo(cwd, agentId, keepCount) {
1546
+ const path = sessionFilePath(cwd, agentId);
1547
+ if (!fs.existsSync(path)) return 0;
1548
+ let kept = 0;
1549
+ await rewriteLockedSession(path, (lines) => {
1550
+ const keep = Math.max(0, Math.min(keepCount, lines.length));
1551
+ kept = keep;
1552
+ if (keep === lines.length) return void 0;
1553
+ return keep === 0 ? "" : `${lines.slice(0, keep).join("\n")}
1554
+ `;
1555
+ });
1556
+ return kept;
1557
+ }
1558
+ var VALID_ROLES;
1559
+ var init_agent_session_store = __esm({
1560
+ "src/internal/runtime/session/agent-session-store.ts"() {
1561
+ init_atomic_write();
1562
+ init_file_lock();
1563
+ init_security();
1564
+ VALID_ROLES = /* @__PURE__ */ new Set([
1565
+ "user",
1566
+ "assistant",
1567
+ "system",
1568
+ "tool_call",
1569
+ "tool_result"
1570
+ ]);
1434
1571
  }
1435
1572
  });
1436
1573
 
1437
- // src/internal/llm/credential-pool-types.ts
1438
- var COOLDOWN_MS, DEFAULT_COOLDOWN_MS;
1439
- var init_credential_pool_types = __esm({
1440
- "src/internal/llm/credential-pool-types.ts"() {
1441
- COOLDOWN_MS = {
1442
- 401: 5 * 60 * 1e3,
1443
- // 5 minutes OAuth refresh can recover quickly
1444
- 402: 60 * 60 * 1e3,
1445
- // 1 hour billing quota typically hourly+
1446
- 429: 60 * 60 * 1e3
1447
- // 1 hour — daily-rate-limit windows
1448
- };
1449
- DEFAULT_COOLDOWN_MS = 60 * 60 * 1e3;
1574
+ // src/internal/persistence/objective-coerce.ts
1575
+ function coerceOptions(raw) {
1576
+ if (typeof raw !== "object" || raw === null) return void 0;
1577
+ const o = raw;
1578
+ const out = {};
1579
+ if (typeof o.maxRuns === "number") out.maxRuns = o.maxRuns;
1580
+ if (typeof o.judgeModel === "string") out.judgeModel = o.judgeModel;
1581
+ if (typeof o.prompt === "string") out.prompt = o.prompt;
1582
+ return Object.keys(out).length > 0 ? out : void 0;
1583
+ }
1584
+ function coerceObjectiveRecord(raw) {
1585
+ if (typeof raw !== "object" || raw === null) return void 0;
1586
+ const o = raw;
1587
+ if (o._schemaVersion !== 1) return void 0;
1588
+ if (typeof o.objective !== "string") return void 0;
1589
+ if (typeof o.runsUsed !== "number") return void 0;
1590
+ if (typeof o.status !== "string" || !STATUSES.includes(o.status))
1591
+ return void 0;
1592
+ const options = coerceOptions(o.options);
1593
+ return {
1594
+ _schemaVersion: 1,
1595
+ objective: o.objective,
1596
+ ...options !== void 0 ? { options } : {},
1597
+ status: o.status,
1598
+ runsUsed: o.runsUsed
1599
+ };
1600
+ }
1601
+ var STATUSES;
1602
+ var init_objective_coerce = __esm({
1603
+ "src/internal/persistence/objective-coerce.ts"() {
1604
+ STATUSES = ["active", "done", "paused"];
1450
1605
  }
1451
1606
  });
1452
1607
 
1453
- // src/internal/llm/retry.ts
1454
- function computeBackoffMs(opts) {
1455
- const base = opts.baseMs ?? DEFAULT_BASE_MS;
1456
- const cap2 = opts.capMs ?? DEFAULT_CAP_MS;
1457
- const rng = opts.rng ?? Math.random;
1608
+ // src/internal/persistence/pagination.ts
1609
+ function paginate(items, opts) {
1610
+ if (opts === void 0 || opts.offset === void 0 && opts.limit === void 0) return items;
1611
+ const start = Math.max(0, opts.offset ?? 0);
1612
+ const end = opts.limit === void 0 ? items.length : start + Math.max(0, opts.limit);
1613
+ return items.slice(start, end);
1614
+ }
1615
+ var init_pagination = __esm({
1616
+ "src/internal/persistence/pagination.ts"() {
1617
+ }
1618
+ });
1619
+
1620
+ // src/internal/persistence/session-meta.ts
1621
+ function applyMetaPatch(current, patch) {
1622
+ const next = {};
1623
+ if (current.title !== void 0) next.title = current.title;
1624
+ if (current.tag !== void 0) next.tag = current.tag;
1625
+ if (patch.title === null) delete next.title;
1626
+ else if (patch.title !== void 0) next.title = patch.title;
1627
+ if (patch.tag === null) delete next.tag;
1628
+ else if (patch.tag !== void 0) next.tag = patch.tag;
1629
+ return next;
1630
+ }
1631
+ function coerceSessionMeta(raw) {
1632
+ if (typeof raw !== "object" || raw === null) return void 0;
1633
+ const obj = raw;
1634
+ const meta = {};
1635
+ if (typeof obj.title === "string") meta.title = obj.title;
1636
+ if (typeof obj.tag === "string") meta.tag = obj.tag;
1637
+ return meta.title === void 0 && meta.tag === void 0 ? void 0 : meta;
1638
+ }
1639
+ var init_session_meta = __esm({
1640
+ "src/internal/persistence/session-meta.ts"() {
1641
+ }
1642
+ });
1643
+ function toStoredMessage(record) {
1644
+ return {
1645
+ role: record.role,
1646
+ content: record.text,
1647
+ at: record.at
1648
+ };
1649
+ }
1650
+ function toRecord(message) {
1651
+ return { role: message.role, text: message.content, at: message.at ?? Date.now() };
1652
+ }
1653
+ exports.FileSystemConversationStorage = void 0;
1654
+ var init_conversation_storage_fs = __esm({
1655
+ "src/internal/persistence/conversation-storage-fs.ts"() {
1656
+ init_agent_session_store();
1657
+ init_security();
1658
+ init_path_guard();
1659
+ init_file_lock();
1660
+ init_objective_coerce();
1661
+ init_pagination();
1662
+ init_session_meta();
1663
+ exports.FileSystemConversationStorage = class {
1664
+ #root;
1665
+ constructor(opts = {}) {
1666
+ this.#root = opts.root ?? process.cwd();
1667
+ }
1668
+ /** Exposed for tests + diagnostics. The path is sanitized at use sites. */
1669
+ get root() {
1670
+ return this.#root;
1671
+ }
1672
+ async getMessages(conversationId, opts) {
1673
+ const records = await readAllPersistedMessages(this.#root, conversationId);
1674
+ const all = records.map(toStoredMessage);
1675
+ return paginate(all, opts);
1676
+ }
1677
+ async appendMessage(conversationId, message) {
1678
+ await appendAnyPersistedMessage(this.#root, conversationId, toRecord(message));
1679
+ }
1680
+ async appendMessages(conversationId, messages) {
1681
+ await appendPersistedMessages(this.#root, conversationId, messages.map(toRecord));
1682
+ }
1683
+ async truncateConversation(conversationId, keepCount) {
1684
+ return truncateSessionTo(this.#root, conversationId, keepCount);
1685
+ }
1686
+ async deleteConversation(conversationId) {
1687
+ const safe2 = sanitizeIdentifier(conversationId, { maxLen: 128 });
1688
+ const dirPath = safePathJoin(this.#root, ".theokit", "agents", safe2);
1689
+ await promises.rm(dirPath, { recursive: true, force: true });
1690
+ }
1691
+ async deleteScope(prefix) {
1692
+ const ids = await this.listConversationIds();
1693
+ const matching = ids.filter((id) => id.startsWith(prefix));
1694
+ for (const id of matching) await this.deleteConversation(id);
1695
+ return matching.length;
1696
+ }
1697
+ async listConversationIds(opts = {}) {
1698
+ const agentsRoot = safePathJoin(this.#root, ".theokit", "agents");
1699
+ let entries;
1700
+ try {
1701
+ entries = await promises.readdir(agentsRoot);
1702
+ } catch (cause) {
1703
+ if (cause.code === "ENOENT") return [];
1704
+ throw cause;
1705
+ }
1706
+ if (opts.limit !== void 0) return entries.slice(0, opts.limit);
1707
+ return entries;
1708
+ }
1709
+ async compact(conversationId, maxTurns) {
1710
+ await compactSessionFile(this.#root, conversationId, maxTurns);
1711
+ }
1712
+ // SE4 — session metadata persisted as a per-conversation sidecar
1713
+ // `<root>/.theokit/agents/<safeId>/session.json` (same sanitized perimeter as
1714
+ // the transcript). Kept separate from messages.jsonl so a title/tag write does
1715
+ // not rewrite the append-only log.
1716
+ #metaPath(conversationId) {
1717
+ const safe2 = sanitizeIdentifier(conversationId, { maxLen: 128 });
1718
+ return safePathJoin(this.#root, ".theokit", "agents", safe2, "session.json");
1719
+ }
1720
+ async getSessionMeta(conversationId) {
1721
+ try {
1722
+ const raw = await promises.readFile(this.#metaPath(conversationId), "utf8");
1723
+ return coerceSessionMeta(JSON.parse(raw));
1724
+ } catch (cause) {
1725
+ if (cause.code === "ENOENT") return void 0;
1726
+ throw cause;
1727
+ }
1728
+ }
1729
+ async setSessionMeta(conversationId, patch) {
1730
+ const metaPath = this.#metaPath(conversationId);
1731
+ await promises.mkdir(path.dirname(metaPath), { recursive: true });
1732
+ await withFileLock(metaPath, async () => {
1733
+ let current = {};
1734
+ try {
1735
+ current = coerceSessionMeta(JSON.parse(await promises.readFile(metaPath, "utf8"))) ?? {};
1736
+ } catch (cause) {
1737
+ if (cause.code !== "ENOENT") throw cause;
1738
+ }
1739
+ const next = applyMetaPatch(current, patch);
1740
+ await promises.writeFile(metaPath, redactSecrets(JSON.stringify(next)), "utf8");
1741
+ });
1742
+ }
1743
+ // SE33 — the durable objective is kept in `objective.json` beside the
1744
+ // transcript (separate from messages.jsonl + session.json). Uses the TOTAL
1745
+ // `safeFilenameForId` (not `sanitizeIdentifier`) so a caller-supplied
1746
+ // `threadId` with exotic characters (e.g. "user@example.com") hashes to a
1747
+ // deterministic dir instead of throwing — honoring the objective methods'
1748
+ // never-throw contract (ADR D6). Conforming ids pass through unchanged, so
1749
+ // the objective still sits beside the transcript for the normal case.
1750
+ #objectivePath(conversationId) {
1751
+ const safe2 = safeFilenameForId(conversationId, { maxLen: 128 });
1752
+ return safePathJoin(this.#root, ".theokit", "agents", safe2, "objective.json");
1753
+ }
1754
+ async getObjectiveRecord(conversationId) {
1755
+ try {
1756
+ const raw = await promises.readFile(this.#objectivePath(conversationId), "utf8");
1757
+ return coerceObjectiveRecord(JSON.parse(raw));
1758
+ } catch (cause) {
1759
+ if (cause.code === "ENOENT") return void 0;
1760
+ throw cause;
1761
+ }
1762
+ }
1763
+ async setObjectiveRecord(conversationId, record) {
1764
+ const path$1 = this.#objectivePath(conversationId);
1765
+ if (record === null) {
1766
+ await promises.rm(path$1, { force: true });
1767
+ return;
1768
+ }
1769
+ await promises.mkdir(path.dirname(path$1), { recursive: true });
1770
+ await withFileLock(path$1, async () => {
1771
+ await promises.writeFile(path$1, redactSecrets(JSON.stringify(record)), "utf8");
1772
+ });
1773
+ }
1774
+ // SE33 (HIGH-1 fix) — atomic read-modify-write: the read that feeds `mutate`
1775
+ // happens INSIDE the same file lock as the write, so two concurrent progress
1776
+ // write-backs on one thread cannot both read a stale `runsUsed` and drop turns.
1777
+ async updateObjectiveRecord(conversationId, mutate) {
1778
+ const path$1 = this.#objectivePath(conversationId);
1779
+ await promises.mkdir(path.dirname(path$1), { recursive: true });
1780
+ await withFileLock(path$1, async () => {
1781
+ let current;
1782
+ try {
1783
+ current = coerceObjectiveRecord(JSON.parse(await promises.readFile(path$1, "utf8")));
1784
+ } catch (cause) {
1785
+ if (cause.code !== "ENOENT") throw cause;
1786
+ }
1787
+ const next = mutate(current);
1788
+ if (next === void 0) return;
1789
+ if (next === null) {
1790
+ await promises.rm(path$1, { force: true });
1791
+ return;
1792
+ }
1793
+ await promises.writeFile(path$1, redactSecrets(JSON.stringify(next)), "utf8");
1794
+ });
1795
+ }
1796
+ async dispose() {
1797
+ }
1798
+ };
1799
+ }
1800
+ });
1801
+ async function withToolWhitelist(whitelist, fn) {
1802
+ return toolWhitelistStore.run(whitelist, fn);
1803
+ }
1804
+ function currentToolWhitelist() {
1805
+ return toolWhitelistStore.getStore();
1806
+ }
1807
+ function checkToolWhitelist(toolName) {
1808
+ const whitelist = currentToolWhitelist();
1809
+ if (whitelist === void 0) return { allowed: true };
1810
+ if (!whitelist.has(toolName)) {
1811
+ return {
1812
+ allowed: false,
1813
+ reason: `Tool "${toolName}" not available in this fork context`
1814
+ };
1815
+ }
1816
+ return { allowed: true };
1817
+ }
1818
+ var toolWhitelistStore;
1819
+ var init_async_local_storage = __esm({
1820
+ "src/internal/runtime/concurrency/async-local-storage.ts"() {
1821
+ toolWhitelistStore = new async_hooks.AsyncLocalStorage();
1822
+ }
1823
+ });
1824
+
1825
+ // src/internal/runtime/concurrency/async-semaphore.ts
1826
+ function createSemaphore(permits) {
1827
+ if (!Number.isInteger(permits) || permits < 1) {
1828
+ throw new exports.ConfigurationError(
1829
+ `async-semaphore: permits must be a positive integer, got ${permits}`,
1830
+ { code: "invalid_concurrency" }
1831
+ );
1832
+ }
1833
+ let active = 0;
1834
+ const queue = [];
1835
+ function tryGrant() {
1836
+ if (active < permits && queue.length > 0) {
1837
+ const resolve3 = queue.shift();
1838
+ if (resolve3 !== void 0) {
1839
+ active += 1;
1840
+ resolve3();
1841
+ }
1842
+ }
1843
+ }
1844
+ return {
1845
+ inFlight: () => active,
1846
+ pending: () => queue.length + active,
1847
+ async acquire() {
1848
+ await new Promise((resolve3) => {
1849
+ queue.push(resolve3);
1850
+ tryGrant();
1851
+ });
1852
+ let released = false;
1853
+ return () => {
1854
+ if (released) return;
1855
+ released = true;
1856
+ active -= 1;
1857
+ tryGrant();
1858
+ };
1859
+ }
1860
+ };
1861
+ }
1862
+ var init_async_semaphore = __esm({
1863
+ "src/internal/runtime/concurrency/async-semaphore.ts"() {
1864
+ init_errors();
1865
+ }
1866
+ });
1867
+
1868
+ // src/internal/llm/credential-pool-types.ts
1869
+ var COOLDOWN_MS, DEFAULT_COOLDOWN_MS;
1870
+ var init_credential_pool_types = __esm({
1871
+ "src/internal/llm/credential-pool-types.ts"() {
1872
+ COOLDOWN_MS = {
1873
+ 401: 5 * 60 * 1e3,
1874
+ // 5 minutes — OAuth refresh can recover quickly
1875
+ 402: 60 * 60 * 1e3,
1876
+ // 1 hour — billing quota typically hourly+
1877
+ 429: 60 * 60 * 1e3
1878
+ // 1 hour — daily-rate-limit windows
1879
+ };
1880
+ DEFAULT_COOLDOWN_MS = 60 * 60 * 1e3;
1881
+ }
1882
+ });
1883
+
1884
+ // src/internal/llm/retry.ts
1885
+ function computeBackoffMs(opts) {
1886
+ const base = opts.baseMs ?? DEFAULT_BASE_MS;
1887
+ const cap2 = opts.capMs ?? DEFAULT_CAP_MS;
1888
+ const rng = opts.rng ?? Math.random;
1458
1889
  if (opts.retryAfterMs !== void 0 && opts.retryAfterMs >= 0) {
1459
1890
  return Math.max(base, Math.min(cap2, opts.retryAfterMs));
1460
1891
  }
@@ -1740,6 +2171,151 @@ var init_credential_pool_context = __esm({
1740
2171
  }
1741
2172
  });
1742
2173
 
2174
+ // src/internal/runtime/objective/objective-store.ts
2175
+ function canPersist(a) {
2176
+ return typeof a.getObjectiveRecord === "function" && typeof a.setObjectiveRecord === "function";
2177
+ }
2178
+ async function mutateObjective(adapter, conversationId, mutate) {
2179
+ if (typeof adapter.updateObjectiveRecord === "function") {
2180
+ await adapter.updateObjectiveRecord(conversationId, mutate);
2181
+ return;
2182
+ }
2183
+ const current = await adapter.getObjectiveRecord(conversationId);
2184
+ const next = mutate(current);
2185
+ if (next === void 0) return;
2186
+ await adapter.setObjectiveRecord(conversationId, next);
2187
+ }
2188
+ async function getObjective(adapter, conversationId) {
2189
+ if (!canPersist(adapter)) return void 0;
2190
+ return adapter.getObjectiveRecord(conversationId);
2191
+ }
2192
+ async function setObjective(adapter, conversationId, objective, options) {
2193
+ if (!canPersist(adapter)) return;
2194
+ const record = {
2195
+ _schemaVersion: 1,
2196
+ objective,
2197
+ ...options !== void 0 ? { options } : {},
2198
+ status: "active",
2199
+ runsUsed: 0
2200
+ };
2201
+ await adapter.setObjectiveRecord(conversationId, record);
2202
+ }
2203
+ async function updateObjectiveOptions(adapter, conversationId, patch) {
2204
+ if (!canPersist(adapter)) return;
2205
+ await mutateObjective(
2206
+ adapter,
2207
+ conversationId,
2208
+ (current) => current === void 0 ? void 0 : { ...current, options: { ...current.options, ...patch } }
2209
+ );
2210
+ }
2211
+ async function writeObjectiveProgress(adapter, conversationId, progress) {
2212
+ if (!canPersist(adapter)) return;
2213
+ await mutateObjective(
2214
+ adapter,
2215
+ conversationId,
2216
+ (current) => current === void 0 ? void 0 : { ...current, runsUsed: progress.runsUsed, status: progress.status }
2217
+ );
2218
+ }
2219
+ async function clearObjective(adapter, conversationId) {
2220
+ if (!canPersist(adapter)) return;
2221
+ await adapter.setObjectiveRecord(conversationId, null);
2222
+ }
2223
+ var init_objective_store = __esm({
2224
+ "src/internal/runtime/objective/objective-store.ts"() {
2225
+ }
2226
+ });
2227
+
2228
+ // src/internal/runtime/local-agent/local-agent-goal-extensions.ts
2229
+ var local_agent_goal_extensions_exports = {};
2230
+ __export(local_agent_goal_extensions_exports, {
2231
+ formatObjectiveProjection: () => formatObjectiveProjection,
2232
+ localAgentClearObjective: () => localAgentClearObjective,
2233
+ localAgentGetObjective: () => localAgentGetObjective,
2234
+ localAgentSetObjective: () => localAgentSetObjective,
2235
+ localAgentUpdateObjectiveOptions: () => localAgentUpdateObjectiveOptions,
2236
+ persistDurableProgress: () => persistDurableProgress,
2237
+ resolveCurrentObjectiveText: () => resolveCurrentObjectiveText,
2238
+ resolveDurableRun: () => resolveDurableRun
2239
+ });
2240
+ function resolveAdapter(handle) {
2241
+ return typeof handle === "string" ? new exports.FileSystemConversationStorage({ root: handle }) : handle;
2242
+ }
2243
+ function assertValidGoalOptions(options) {
2244
+ if (options.maxRuns !== void 0 && (!Number.isInteger(options.maxRuns) || options.maxRuns <= 0)) {
2245
+ throw new exports.ConfigurationError(`maxRuns must be a positive integer, got ${options.maxRuns}`, {
2246
+ code: "invalid_objective_max_runs"
2247
+ });
2248
+ }
2249
+ }
2250
+ async function localAgentSetObjective(handle, objective, opts) {
2251
+ const { threadId, ...options } = opts;
2252
+ assertValidGoalOptions(options);
2253
+ await setObjective(
2254
+ resolveAdapter(handle),
2255
+ threadId,
2256
+ objective,
2257
+ Object.keys(options).length > 0 ? options : void 0
2258
+ );
2259
+ }
2260
+ async function localAgentGetObjective(handle, opts) {
2261
+ return getObjective(resolveAdapter(handle), opts.threadId);
2262
+ }
2263
+ async function localAgentUpdateObjectiveOptions(handle, opts) {
2264
+ const { threadId, ...patch } = opts;
2265
+ assertValidGoalOptions(patch);
2266
+ await updateObjectiveOptions(resolveAdapter(handle), threadId, patch);
2267
+ }
2268
+ async function localAgentClearObjective(handle, opts) {
2269
+ await clearObjective(resolveAdapter(handle), opts.threadId);
2270
+ }
2271
+ async function resolveCurrentObjectiveText(handle, threadId) {
2272
+ const record = await getObjective(resolveAdapter(handle), threadId);
2273
+ return record?.status === "active" ? record.objective : void 0;
2274
+ }
2275
+ function formatObjectiveProjection(objective, assembled) {
2276
+ const signal = `<current-objective>
2277
+ ${objective}
2278
+ </current-objective>`;
2279
+ return assembled === void 0 || assembled.length === 0 ? signal : `${signal}
2280
+
2281
+ ${assembled}`;
2282
+ }
2283
+ async function resolveDurableRun(handle, goalConfig, threadId, callerOptions) {
2284
+ const record = await getObjective(resolveAdapter(handle), threadId);
2285
+ if (record === void 0) return { kind: "none" };
2286
+ const judgeModel = callerOptions?.judgeModel ?? record.options?.judgeModel ?? goalConfig?.judgeModel;
2287
+ if (judgeModel === void 0) return { kind: "inert" };
2288
+ const totalBudget = record.options?.maxRuns ?? goalConfig?.maxRuns ?? DEFAULT_MAX_RUNS;
2289
+ const remaining = Math.max(0, totalBudget - record.runsUsed);
2290
+ if (remaining === 0) return { kind: "exhausted" };
2291
+ const perCall = callerOptions?.maxTurns;
2292
+ const maxTurns = perCall !== void 0 ? Math.min(perCall, remaining) : remaining;
2293
+ return {
2294
+ kind: "run",
2295
+ goal: record.objective,
2296
+ options: { ...callerOptions, maxTurns, judgeModel }
2297
+ };
2298
+ }
2299
+ async function persistDurableProgress(handle, threadId, result) {
2300
+ const adapter = resolveAdapter(handle);
2301
+ const record = await getObjective(adapter, threadId);
2302
+ if (record === void 0) return;
2303
+ const status = result.status === "completed" ? "done" : result.status === "paused" ? "paused" : "active";
2304
+ await writeObjectiveProgress(adapter, threadId, {
2305
+ runsUsed: record.runsUsed + result.turnsUsed,
2306
+ status
2307
+ });
2308
+ }
2309
+ var DEFAULT_MAX_RUNS;
2310
+ var init_local_agent_goal_extensions = __esm({
2311
+ "src/internal/runtime/local-agent/local-agent-goal-extensions.ts"() {
2312
+ init_errors();
2313
+ init_conversation_storage_fs();
2314
+ init_objective_store();
2315
+ DEFAULT_MAX_RUNS = 20;
2316
+ }
2317
+ });
2318
+
1743
2319
  // src/internal/memory/embedding-cache.ts
1744
2320
  var LruEmbeddingCache, globalEmbeddingCache;
1745
2321
  var init_embedding_cache = __esm({
@@ -7923,68 +8499,7 @@ init_cwd_mutex();
7923
8499
 
7924
8500
  // src/internal/personality/store.ts
7925
8501
  init_atomic_write();
7926
-
7927
- // src/internal/persistence/file-lock.ts
7928
- init_cwd_mutex();
7929
- var cached;
7930
- var warnedMissing = false;
7931
- var warnedStructural = false;
7932
- async function getProperLockfile() {
7933
- if (cached !== void 0) return cached;
7934
- try {
7935
- const mod = await import('proper-lockfile');
7936
- if (!validateLockModule(mod)) {
7937
- if (!warnedStructural) {
7938
- warnedStructural = true;
7939
- process.stderr.write(
7940
- "[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"
7941
- );
7942
- }
7943
- cached = null;
7944
- return cached;
7945
- }
7946
- cached = mod;
7947
- } catch {
7948
- cached = null;
7949
- }
7950
- return cached;
7951
- }
7952
- function validateLockModule(mod) {
7953
- if (mod === null || mod === void 0 || typeof mod !== "object") return false;
7954
- const m = mod;
7955
- return typeof m.lock === "function" && typeof m.unlock === "function";
7956
- }
7957
- async function withFileLock(path, fn, options) {
7958
- const lib = await getProperLockfile();
7959
- if (lib === null) {
7960
- if (!warnedMissing) {
7961
- warnedMissing = true;
7962
- process.stderr.write(
7963
- "[theokit-sdk] proper-lockfile not installed; cross-process file lock unavailable. Install with: pnpm add proper-lockfile\n"
7964
- );
7965
- }
7966
- return withCwdMutex(`file-lock:${path}`, fn);
7967
- }
7968
- return withCwdMutex(`file-lock:${path}`, async () => {
7969
- const release = await lib.lock(path, {
7970
- // EC-1: companion lockfile, target path may not exist yet.
7971
- lockfilePath: `${path}.lock`,
7972
- realpath: false,
7973
- stale: 3e4,
7974
- retries: {
7975
- retries: 5,
7976
- factor: 1.5,
7977
- minTimeout: 100,
7978
- maxTimeout: 5e3
7979
- }
7980
- });
7981
- try {
7982
- return await fn();
7983
- } finally {
7984
- await release();
7985
- }
7986
- });
7987
- }
8502
+ init_file_lock();
7988
8503
  var THEOKIT_DIR_NAME = ".theokit";
7989
8504
  function getTheokitHome(cwd) {
7990
8505
  const override = process.env.THEOKIT_HOME?.trim();
@@ -8885,405 +9400,148 @@ function spawnAndCollect(options) {
8885
9400
  };
8886
9401
  const timer = setTimeout(() => {
8887
9402
  timedOut = true;
8888
- try {
8889
- child.kill("SIGKILL");
8890
- } catch {
8891
- }
8892
- settle({ stdout, stderr, exitCode: null, timedOut: true });
8893
- }, timeoutMs);
8894
- child.stdout?.on("data", (chunk) => {
8895
- stdout += chunk.toString("utf8");
8896
- });
8897
- child.stderr?.on("data", (chunk) => {
8898
- stderr += chunk.toString("utf8");
8899
- });
8900
- child.on("error", (cause) => {
8901
- clearTimeout(timer);
8902
- settle({ stdout, stderr, exitCode: -1, timedOut, spawnError: cause });
8903
- });
8904
- child.on("close", (code) => {
8905
- clearTimeout(timer);
8906
- settle({ stdout, stderr, exitCode: code, timedOut });
8907
- });
8908
- if (options.stdin !== void 0 && child.stdin !== null) {
8909
- child.stdin.end(options.stdin);
8910
- }
8911
- });
8912
- }
8913
-
8914
- // src/internal/runtime/hooks/hooks-executor.ts
8915
- init_hooks_source();
8916
- var HooksExecutor = class {
8917
- constructor(cwd) {
8918
- this.cwd = cwd;
8919
- }
8920
- cwd;
8921
- config = {};
8922
- async initialize(settingSourcesIncludeProject) {
8923
- if (!settingSourcesIncludeProject) {
8924
- this.config = {};
8925
- return;
8926
- }
8927
- this.config = await loadHookConfig(this.cwd);
8928
- }
8929
- /** Fire every hook registered for `event` and aggregate the decisions. */
8930
- async run(payload) {
8931
- const commands = this.commandsFor(payload.event, payload.tool);
8932
- if (commands.length === 0) return { decisions: [], blocked: false };
8933
- const decisions = [];
8934
- for (const command of commands) {
8935
- const decision = await this.executeOne(command, payload);
8936
- decisions.push(decision);
8937
- if (decision.decision === "deny") {
8938
- const result = {
8939
- decisions,
8940
- blocked: true
8941
- };
8942
- if (decision.reason !== void 0) result.reason = decision.reason;
8943
- return result;
8944
- }
8945
- }
8946
- return { decisions, blocked: false };
8947
- }
8948
- commandsFor(event, tool) {
8949
- const list2 = this.config.hooks?.[event] ?? [];
8950
- if (tool === void 0) return list2;
8951
- return list2.filter((entry) => {
8952
- if (entry.matcher === void 0) return true;
8953
- try {
8954
- return new RegExp(entry.matcher).test(tool);
8955
- } catch {
8956
- return entry.matcher === tool;
8957
- }
8958
- });
8959
- }
8960
- async executeOne(command, payload) {
8961
- const timeoutMs = command.timeoutMs ?? 3e4;
8962
- const result = await spawnAndCollect({
8963
- command: "sh",
8964
- args: ["-c", command.command],
8965
- cwd: this.cwd,
8966
- timeoutMs,
8967
- stdin: JSON.stringify(payload)
8968
- });
8969
- if (result.timedOut) {
8970
- return { decision: "deny", reason: `Hook timed out after ${timeoutMs}ms` };
8971
- }
8972
- if (result.spawnError !== void 0) {
8973
- return { decision: "deny", reason: `Hook spawn failed: ${result.spawnError.message}` };
8974
- }
8975
- if (result.exitCode !== 0) {
8976
- return {
8977
- decision: "deny",
8978
- reason: result.stderr.trim().length > 0 ? result.stderr.trim() : `Hook exited with code ${result.exitCode}`
8979
- };
8980
- }
8981
- return parseDecisionFromStdout(result.stdout);
8982
- }
8983
- };
8984
- function parseDecisionFromStdout(stdout) {
8985
- const trimmed = stdout.trim();
8986
- if (trimmed.length === 0) return { decision: "allow" };
8987
- try {
8988
- const parsed = JSON.parse(trimmed);
8989
- if (parsed.decision === "deny" || parsed.decision === "feedback") {
8990
- const result = { decision: parsed.decision };
8991
- if (parsed.reason !== void 0) result.reason = parsed.reason;
8992
- if (parsed.feedback !== void 0) result.feedback = parsed.feedback;
8993
- return result;
8994
- }
8995
- if (parsed.decision === "allow") return { decision: "allow" };
8996
- } catch {
8997
- return { decision: "feedback", feedback: trimmed };
8998
- }
8999
- return { decision: "allow" };
9000
- }
9001
-
9002
- // src/internal/runtime/lifecycle/post-run-lifecycle.ts
9003
- init_session_summary_writer();
9004
-
9005
- // src/internal/runtime/memory/memory-path-selector.ts
9006
- var PORT_MEMORY_PATH_ENV_VAR = "THEOKIT_PORT_MEMORY_PATH";
9007
- function shouldUsePortMemoryPath() {
9008
- const env = globalThis.process?.env;
9009
- if (env === void 0) return false;
9010
- const val = env[PORT_MEMORY_PATH_ENV_VAR];
9011
- return val === "1" || val === "true";
9012
- }
9013
- function resolveMemoryProviderForLoop(consumerSupplied, defaultAdapter, portPathEnabled) {
9014
- if (consumerSupplied !== void 0) return consumerSupplied;
9015
- if (portPathEnabled) return defaultAdapter;
9016
- return void 0;
9017
- }
9018
- function resolveMemoryToolsForLoop(legacyTools, portPathEnabled) {
9019
- if (portPathEnabled) return void 0;
9020
- return legacyTools;
9021
- }
9022
- function resolveActiveMemorySummaryForSend(legacySummary, portPathEnabled) {
9023
- if (portPathEnabled) return void 0;
9024
- return legacySummary;
9025
- }
9026
-
9027
- // src/internal/runtime/session/agent-session-store.ts
9028
- init_atomic_write();
9029
- init_security();
9030
- var VALID_ROLES = /* @__PURE__ */ new Set([
9031
- "user",
9032
- "assistant",
9033
- "system",
9034
- "tool_call",
9035
- "tool_result"
9036
- ]);
9037
- function sessionFilePath(cwd, agentId) {
9038
- const safe2 = sanitizeIdentifier(agentId, { maxLen: 128 });
9039
- return safePathJoin(cwd, ".theokit", "agents", safe2, "messages.jsonl");
9040
- }
9041
- async function readJsonlLines(cwd, agentId) {
9042
- const path = sessionFilePath(cwd, agentId);
9043
- try {
9044
- const raw = await promises.readFile(path, "utf8");
9045
- return raw.split("\n").filter((line) => line.length > 0);
9046
- } catch {
9047
- return [];
9048
- }
9049
- }
9050
- function warnMalformed(agentId, line) {
9051
- process.stderr.write(
9052
- `[theokit-sdk] skipping malformed line in messages.jsonl (${agentId}): ${line.slice(0, 80)}...
9053
- `
9054
- );
9055
- }
9056
- function hydrateSessionLine(parsed) {
9057
- if (typeof parsed.text !== "string" || parsed.role === void 0) return void 0;
9058
- if (parsed.role === "user" || parsed.role === "assistant") {
9059
- return { role: parsed.role, text: parsed.text };
9060
- }
9061
- if (parsed.role === "tool_call" || parsed.role === "tool_result") {
9062
- const label = parsed.role === "tool_call" ? "tool call" : "tool result";
9063
- return { role: "assistant", text: `[${label}] ${parsed.text}` };
9064
- }
9065
- return void 0;
9066
- }
9067
- async function readSessionFile(cwd, agentId) {
9068
- const lines = await readJsonlLines(cwd, agentId);
9069
- const messages = [];
9070
- for (const line of lines) {
9071
- try {
9072
- const msg = hydrateSessionLine(JSON.parse(line));
9073
- if (msg !== void 0) messages.push(msg);
9074
- } catch {
9075
- warnMalformed(agentId, line);
9076
- }
9077
- }
9078
- return messages;
9079
- }
9080
- async function readAllPersistedMessages(cwd, agentId) {
9081
- const lines = await readJsonlLines(cwd, agentId);
9082
- const messages = [];
9083
- for (const line of lines) {
9084
- try {
9085
- const parsed = JSON.parse(line);
9086
- if (parsed.role !== void 0 && VALID_ROLES.has(parsed.role) && typeof parsed.text === "string") {
9087
- messages.push({
9088
- role: parsed.role,
9089
- text: parsed.text,
9090
- at: typeof parsed.at === "number" ? parsed.at : Date.now()
9091
- });
9092
- }
9093
- } catch {
9094
- warnMalformed(agentId, line);
9095
- }
9096
- }
9097
- return messages;
9098
- }
9099
- async function appendAnyPersistedMessage(cwd, agentId, record) {
9100
- await appendPersistedMessages(cwd, agentId, [record]);
9101
- }
9102
- async function appendPersistedMessages(cwd, agentId, records) {
9103
- if (records.length === 0) return;
9104
- const path$1 = sessionFilePath(cwd, agentId);
9105
- const payload = records.map((r) => `${redactSecrets(JSON.stringify(r))}
9106
- `).join("");
9107
- const dir = path.dirname(path$1);
9108
- let written = false;
9109
- const attempt = async () => {
9110
- await promises.mkdir(dir, { recursive: true });
9111
- await withFileLock(path$1, async () => {
9112
- await promises.appendFile(path$1, payload, "utf8");
9113
- written = true;
9114
- });
9115
- };
9116
- try {
9117
- await attempt();
9118
- } catch (cause) {
9119
- if (written || cause.code !== "ENOENT") throw cause;
9120
- await attempt();
9121
- }
9122
- }
9123
- async function rewriteLockedSession(path, transform) {
9124
- await withFileLock(path, async () => {
9125
- let raw;
9126
- try {
9127
- raw = await promises.readFile(path, "utf8");
9128
- } catch {
9129
- return;
9130
- }
9131
- const lines = raw.split("\n").filter((line) => line.length > 0);
9132
- const next = transform(lines);
9133
- if (next === void 0) return;
9134
- await replaceFileAtomic(path, next);
9135
- });
9136
- }
9137
- async function compactSessionFile(cwd, agentId, maxTurns) {
9138
- const path = sessionFilePath(cwd, agentId);
9139
- if (!fs.existsSync(path)) return;
9140
- await rewriteLockedSession(
9141
- path,
9142
- (lines) => lines.length <= maxTurns * 2 ? void 0 : `${lines.slice(-maxTurns).join("\n")}
9143
- `
9144
- );
9145
- }
9146
- async function truncateSessionTo(cwd, agentId, keepCount) {
9147
- const path = sessionFilePath(cwd, agentId);
9148
- if (!fs.existsSync(path)) return 0;
9149
- let kept = 0;
9150
- await rewriteLockedSession(path, (lines) => {
9151
- const keep = Math.max(0, Math.min(keepCount, lines.length));
9152
- kept = keep;
9153
- if (keep === lines.length) return void 0;
9154
- return keep === 0 ? "" : `${lines.slice(0, keep).join("\n")}
9155
- `;
9156
- });
9157
- return kept;
9158
- }
9159
-
9160
- // src/internal/persistence/conversation-storage-fs.ts
9161
- init_security();
9162
-
9163
- // src/internal/persistence/pagination.ts
9164
- function paginate(items, opts) {
9165
- if (opts === void 0 || opts.offset === void 0 && opts.limit === void 0) return items;
9166
- const start = Math.max(0, opts.offset ?? 0);
9167
- const end = opts.limit === void 0 ? items.length : start + Math.max(0, opts.limit);
9168
- return items.slice(start, end);
9169
- }
9170
-
9171
- // src/internal/persistence/session-meta.ts
9172
- function applyMetaPatch(current, patch) {
9173
- const next = {};
9174
- if (current.title !== void 0) next.title = current.title;
9175
- if (current.tag !== void 0) next.tag = current.tag;
9176
- if (patch.title === null) delete next.title;
9177
- else if (patch.title !== void 0) next.title = patch.title;
9178
- if (patch.tag === null) delete next.tag;
9179
- else if (patch.tag !== void 0) next.tag = patch.tag;
9180
- return next;
9181
- }
9182
- function coerceSessionMeta(raw) {
9183
- if (typeof raw !== "object" || raw === null) return void 0;
9184
- const obj = raw;
9185
- const meta = {};
9186
- if (typeof obj.title === "string") meta.title = obj.title;
9187
- if (typeof obj.tag === "string") meta.tag = obj.tag;
9188
- return meta.title === void 0 && meta.tag === void 0 ? void 0 : meta;
9403
+ try {
9404
+ child.kill("SIGKILL");
9405
+ } catch {
9406
+ }
9407
+ settle({ stdout, stderr, exitCode: null, timedOut: true });
9408
+ }, timeoutMs);
9409
+ child.stdout?.on("data", (chunk) => {
9410
+ stdout += chunk.toString("utf8");
9411
+ });
9412
+ child.stderr?.on("data", (chunk) => {
9413
+ stderr += chunk.toString("utf8");
9414
+ });
9415
+ child.on("error", (cause) => {
9416
+ clearTimeout(timer);
9417
+ settle({ stdout, stderr, exitCode: -1, timedOut, spawnError: cause });
9418
+ });
9419
+ child.on("close", (code) => {
9420
+ clearTimeout(timer);
9421
+ settle({ stdout, stderr, exitCode: code, timedOut });
9422
+ });
9423
+ if (options.stdin !== void 0 && child.stdin !== null) {
9424
+ child.stdin.end(options.stdin);
9425
+ }
9426
+ });
9189
9427
  }
9190
9428
 
9191
- // src/internal/persistence/conversation-storage-fs.ts
9192
- var FileSystemConversationStorage = class {
9193
- #root;
9194
- constructor(opts = {}) {
9195
- this.#root = opts.root ?? process.cwd();
9196
- }
9197
- /** Exposed for tests + diagnostics. The path is sanitized at use sites. */
9198
- get root() {
9199
- return this.#root;
9200
- }
9201
- async getMessages(conversationId, opts) {
9202
- const records = await readAllPersistedMessages(this.#root, conversationId);
9203
- const all = records.map(toStoredMessage);
9204
- return paginate(all, opts);
9205
- }
9206
- async appendMessage(conversationId, message) {
9207
- await appendAnyPersistedMessage(this.#root, conversationId, toRecord(message));
9208
- }
9209
- async appendMessages(conversationId, messages) {
9210
- await appendPersistedMessages(this.#root, conversationId, messages.map(toRecord));
9211
- }
9212
- async truncateConversation(conversationId, keepCount) {
9213
- return truncateSessionTo(this.#root, conversationId, keepCount);
9214
- }
9215
- async deleteConversation(conversationId) {
9216
- const safe2 = sanitizeIdentifier(conversationId, { maxLen: 128 });
9217
- const dirPath = safePathJoin(this.#root, ".theokit", "agents", safe2);
9218
- await promises.rm(dirPath, { recursive: true, force: true });
9429
+ // src/internal/runtime/hooks/hooks-executor.ts
9430
+ init_hooks_source();
9431
+ var HooksExecutor = class {
9432
+ constructor(cwd) {
9433
+ this.cwd = cwd;
9219
9434
  }
9220
- async deleteScope(prefix) {
9221
- const ids = await this.listConversationIds();
9222
- const matching = ids.filter((id) => id.startsWith(prefix));
9223
- for (const id of matching) await this.deleteConversation(id);
9224
- return matching.length;
9225
- }
9226
- async listConversationIds(opts = {}) {
9227
- const agentsRoot = safePathJoin(this.#root, ".theokit", "agents");
9228
- let entries;
9229
- try {
9230
- entries = await promises.readdir(agentsRoot);
9231
- } catch (cause) {
9232
- if (cause.code === "ENOENT") return [];
9233
- throw cause;
9435
+ cwd;
9436
+ config = {};
9437
+ async initialize(settingSourcesIncludeProject) {
9438
+ if (!settingSourcesIncludeProject) {
9439
+ this.config = {};
9440
+ return;
9234
9441
  }
9235
- if (opts.limit !== void 0) return entries.slice(0, opts.limit);
9236
- return entries;
9237
- }
9238
- async compact(conversationId, maxTurns) {
9239
- await compactSessionFile(this.#root, conversationId, maxTurns);
9240
- }
9241
- // SE4 — session metadata persisted as a per-conversation sidecar
9242
- // `<root>/.theokit/agents/<safeId>/session.json` (same sanitized perimeter as
9243
- // the transcript). Kept separate from messages.jsonl so a title/tag write does
9244
- // not rewrite the append-only log.
9245
- #metaPath(conversationId) {
9246
- const safe2 = sanitizeIdentifier(conversationId, { maxLen: 128 });
9247
- return safePathJoin(this.#root, ".theokit", "agents", safe2, "session.json");
9442
+ this.config = await loadHookConfig(this.cwd);
9248
9443
  }
9249
- async getSessionMeta(conversationId) {
9250
- try {
9251
- const raw = await promises.readFile(this.#metaPath(conversationId), "utf8");
9252
- return coerceSessionMeta(JSON.parse(raw));
9253
- } catch (cause) {
9254
- if (cause.code === "ENOENT") return void 0;
9255
- throw cause;
9444
+ /** Fire every hook registered for `event` and aggregate the decisions. */
9445
+ async run(payload) {
9446
+ const commands = this.commandsFor(payload.event, payload.tool);
9447
+ if (commands.length === 0) return { decisions: [], blocked: false };
9448
+ const decisions = [];
9449
+ for (const command of commands) {
9450
+ const decision = await this.executeOne(command, payload);
9451
+ decisions.push(decision);
9452
+ if (decision.decision === "deny") {
9453
+ const result = {
9454
+ decisions,
9455
+ blocked: true
9456
+ };
9457
+ if (decision.reason !== void 0) result.reason = decision.reason;
9458
+ return result;
9459
+ }
9256
9460
  }
9461
+ return { decisions, blocked: false };
9257
9462
  }
9258
- async setSessionMeta(conversationId, patch) {
9259
- const metaPath = this.#metaPath(conversationId);
9260
- await promises.mkdir(path.dirname(metaPath), { recursive: true });
9261
- await withFileLock(metaPath, async () => {
9262
- let current = {};
9463
+ commandsFor(event, tool) {
9464
+ const list2 = this.config.hooks?.[event] ?? [];
9465
+ if (tool === void 0) return list2;
9466
+ return list2.filter((entry) => {
9467
+ if (entry.matcher === void 0) return true;
9263
9468
  try {
9264
- current = coerceSessionMeta(JSON.parse(await promises.readFile(metaPath, "utf8"))) ?? {};
9265
- } catch (cause) {
9266
- if (cause.code !== "ENOENT") throw cause;
9469
+ return new RegExp(entry.matcher).test(tool);
9470
+ } catch {
9471
+ return entry.matcher === tool;
9267
9472
  }
9268
- const next = applyMetaPatch(current, patch);
9269
- await promises.writeFile(metaPath, redactSecrets(JSON.stringify(next)), "utf8");
9270
9473
  });
9271
9474
  }
9272
- async dispose() {
9475
+ async executeOne(command, payload) {
9476
+ const timeoutMs = command.timeoutMs ?? 3e4;
9477
+ const result = await spawnAndCollect({
9478
+ command: "sh",
9479
+ args: ["-c", command.command],
9480
+ cwd: this.cwd,
9481
+ timeoutMs,
9482
+ stdin: JSON.stringify(payload)
9483
+ });
9484
+ if (result.timedOut) {
9485
+ return { decision: "deny", reason: `Hook timed out after ${timeoutMs}ms` };
9486
+ }
9487
+ if (result.spawnError !== void 0) {
9488
+ return { decision: "deny", reason: `Hook spawn failed: ${result.spawnError.message}` };
9489
+ }
9490
+ if (result.exitCode !== 0) {
9491
+ return {
9492
+ decision: "deny",
9493
+ reason: result.stderr.trim().length > 0 ? result.stderr.trim() : `Hook exited with code ${result.exitCode}`
9494
+ };
9495
+ }
9496
+ return parseDecisionFromStdout(result.stdout);
9273
9497
  }
9274
9498
  };
9275
- function toStoredMessage(record) {
9276
- return {
9277
- role: record.role,
9278
- content: record.text,
9279
- at: record.at
9280
- };
9499
+ function parseDecisionFromStdout(stdout) {
9500
+ const trimmed = stdout.trim();
9501
+ if (trimmed.length === 0) return { decision: "allow" };
9502
+ try {
9503
+ const parsed = JSON.parse(trimmed);
9504
+ if (parsed.decision === "deny" || parsed.decision === "feedback") {
9505
+ const result = { decision: parsed.decision };
9506
+ if (parsed.reason !== void 0) result.reason = parsed.reason;
9507
+ if (parsed.feedback !== void 0) result.feedback = parsed.feedback;
9508
+ return result;
9509
+ }
9510
+ if (parsed.decision === "allow") return { decision: "allow" };
9511
+ } catch {
9512
+ return { decision: "feedback", feedback: trimmed };
9513
+ }
9514
+ return { decision: "allow" };
9281
9515
  }
9282
- function toRecord(message) {
9283
- return { role: message.role, text: message.content, at: message.at ?? Date.now() };
9516
+
9517
+ // src/internal/runtime/lifecycle/post-run-lifecycle.ts
9518
+ init_session_summary_writer();
9519
+
9520
+ // src/internal/runtime/memory/memory-path-selector.ts
9521
+ var PORT_MEMORY_PATH_ENV_VAR = "THEOKIT_PORT_MEMORY_PATH";
9522
+ function shouldUsePortMemoryPath() {
9523
+ const env = globalThis.process?.env;
9524
+ if (env === void 0) return false;
9525
+ const val = env[PORT_MEMORY_PATH_ENV_VAR];
9526
+ return val === "1" || val === "true";
9527
+ }
9528
+ function resolveMemoryProviderForLoop(consumerSupplied, defaultAdapter, portPathEnabled) {
9529
+ if (consumerSupplied !== void 0) return consumerSupplied;
9530
+ if (portPathEnabled) return defaultAdapter;
9531
+ return void 0;
9532
+ }
9533
+ function resolveMemoryToolsForLoop(legacyTools, portPathEnabled) {
9534
+ if (portPathEnabled) return void 0;
9535
+ return legacyTools;
9536
+ }
9537
+ function resolveActiveMemorySummaryForSend(legacySummary, portPathEnabled) {
9538
+ if (portPathEnabled) return void 0;
9539
+ return legacySummary;
9284
9540
  }
9285
9541
 
9286
9542
  // src/internal/runtime/session/agent-session.ts
9543
+ init_conversation_storage_fs();
9544
+ init_agent_session_store();
9287
9545
  var DEFAULT_MAX_TURNS = 200;
9288
9546
  var COMPACTION_CHECK_INTERVAL = 50;
9289
9547
  var sessions = /* @__PURE__ */ new Map();
@@ -9299,7 +9557,7 @@ function resolveStorage(cwdOrStorage) {
9299
9557
  if (existing !== void 0) {
9300
9558
  return { adapter: existing, key: `cwd:${cwdOrStorage}` };
9301
9559
  }
9302
- const fresh = new FileSystemConversationStorage({ root: cwdOrStorage });
9560
+ const fresh = new exports.FileSystemConversationStorage({ root: cwdOrStorage });
9303
9561
  fsAdapterByCwd.set(cwdOrStorage, fresh);
9304
9562
  return { adapter: fresh, key: `cwd:${cwdOrStorage}` };
9305
9563
  }
@@ -9361,7 +9619,7 @@ async function hydrateSession(agentId, cwdOrStorage) {
9361
9619
  }
9362
9620
  }
9363
9621
  async function readPersistedForCache(adapter, agentId) {
9364
- if (adapter instanceof FileSystemConversationStorage) {
9622
+ if (adapter instanceof exports.FileSystemConversationStorage) {
9365
9623
  return readSessionFile(adapter.root, agentId);
9366
9624
  }
9367
9625
  const records = await adapter.getMessages(agentId);
@@ -9391,7 +9649,7 @@ async function compactSession(agentId, cwdOrStorage) {
9391
9649
  const { adapter, key: storageId } = resolveStorage(cwdOrStorage);
9392
9650
  const key2 = sessionKey(agentId, storageId);
9393
9651
  const chained = (pendingAppends.get(key2) ?? Promise.resolve()).then(async () => {
9394
- if (adapter instanceof FileSystemConversationStorage) {
9652
+ if (adapter instanceof exports.FileSystemConversationStorage) {
9395
9653
  await compactSessionFile(adapter.root, agentId, DEFAULT_MAX_TURNS);
9396
9654
  } else if (adapter.compact !== void 0) {
9397
9655
  await adapter.compact(agentId, DEFAULT_MAX_TURNS);
@@ -16360,6 +16618,9 @@ async function readProjectMcpServers(cwd) {
16360
16618
  }
16361
16619
  }
16362
16620
 
16621
+ // src/internal/runtime/local-agent/local-agent.ts
16622
+ init_local_agent_goal_extensions();
16623
+
16363
16624
  // src/internal/runtime/local-agent/local-agent-invalidate.ts
16364
16625
  async function applyDeferredInvalidation(agentId, pending, refresh) {
16365
16626
  process.stderr.write(
@@ -17742,16 +18003,49 @@ async function localAgentUsePersonality(args) {
17742
18003
  init_local_agent_plugins();
17743
18004
 
17744
18005
  // src/internal/runtime/local-agent/local-agent-runtime-extensions.ts
17745
- function localAgentRunUntil(agent, goal, options) {
18006
+ function pausedReason(kind, threadId) {
18007
+ switch (kind) {
18008
+ case "none":
18009
+ return `no durable objective set for thread "${threadId}" \u2014 call setObjective() first`;
18010
+ case "inert":
18011
+ return `durable objective for thread "${threadId}" is inert \u2014 no judge resolved (set a judge to activate)`;
18012
+ case "exhausted":
18013
+ return `durable objective for thread "${threadId}" exhausted its run budget \u2014 raise maxRuns to resume`;
18014
+ }
18015
+ }
18016
+ function localAgentRunUntil(agent, goal, options, durable) {
17746
18017
  async function* wrap() {
17747
18018
  const { runUntilImpl: runUntilImpl2 } = await Promise.resolve().then(() => (init_run_until(), run_until_exports));
17748
- const { judgeCallImpl: judgeCallImpl2 } = await Promise.resolve().then(() => (init_judge_call(), judge_call_exports));
17749
- const { getAgentFacade: getAgentFacade2 } = await Promise.resolve().then(() => (init_agent_factory_registry(), agent_factory_registry_exports));
17750
- const create = getAgentFacade2().create;
17751
- const deps = {
17752
- judge: async (ctx, opts) => judgeCallImpl2(ctx, opts, { create })
18019
+ const buildDeps = async () => {
18020
+ const { judgeCallImpl: judgeCallImpl2 } = await Promise.resolve().then(() => (init_judge_call(), judge_call_exports));
18021
+ const { getAgentFacade: getAgentFacade2 } = await Promise.resolve().then(() => (init_agent_factory_registry(), agent_factory_registry_exports));
18022
+ const create = getAgentFacade2().create;
18023
+ return {
18024
+ judge: (ctx, opts) => judgeCallImpl2(ctx, opts, { create })
18025
+ };
17753
18026
  };
17754
- return yield* runUntilImpl2(agent, goal, options, deps);
18027
+ if (goal !== void 0) {
18028
+ return yield* runUntilImpl2(agent, goal, options, await buildDeps());
18029
+ }
18030
+ const threadId = options?.threadId;
18031
+ if (durable === void 0 || threadId === void 0) {
18032
+ const reason = "runUntil() called with no goal and no threadId \u2014 nothing to resolve a durable objective from";
18033
+ yield { type: "status_change", status: "paused", reason };
18034
+ return { status: "paused", turnsUsed: 0, finalResponse: void 0 };
18035
+ }
18036
+ const { resolveDurableRun: resolveDurableRun2, persistDurableProgress: persistDurableProgress2 } = await Promise.resolve().then(() => (init_local_agent_goal_extensions(), local_agent_goal_extensions_exports));
18037
+ const resolved = await resolveDurableRun2(durable.handle, durable.goalConfig, threadId, options);
18038
+ if (resolved.kind !== "run") {
18039
+ yield {
18040
+ type: "status_change",
18041
+ status: "paused",
18042
+ reason: pausedReason(resolved.kind, threadId)
18043
+ };
18044
+ return { status: "paused", turnsUsed: 0, finalResponse: void 0 };
18045
+ }
18046
+ const result = yield* runUntilImpl2(agent, resolved.goal, resolved.options, await buildDeps());
18047
+ await persistDurableProgress2(durable.handle, threadId, result);
18048
+ return result;
17755
18049
  }
17756
18050
  return wrap();
17757
18051
  }
@@ -17817,6 +18111,44 @@ function ponyfillAny(signals) {
17817
18111
  return ctrl.signal;
17818
18112
  }
17819
18113
 
18114
+ // src/internal/runtime/lifecycle/wrap-completion-check-run.ts
18115
+ function wrapRunWithCompletionCheck(args) {
18116
+ const check = args.completionCheck;
18117
+ if (check === void 0) return args.run;
18118
+ const compute = async () => {
18119
+ const result = await args.run.wait();
18120
+ if (result.status !== "finished" || result.result === void 0) return result;
18121
+ const judgeOpts = {};
18122
+ if (check.judgeModel !== void 0) judgeOpts.judgeModel = check.judgeModel;
18123
+ if (check.apiKey !== void 0) judgeOpts.apiKey = check.apiKey;
18124
+ const verdict = await args.deps.judge(
18125
+ { goal: check.criteria, lastResponse: result.result },
18126
+ judgeOpts
18127
+ );
18128
+ const complete = !verdict.parseFailed && verdict.verdict === "done";
18129
+ emitRunEvent(args.onRunEvent, {
18130
+ type: "completion_check",
18131
+ complete,
18132
+ reason: verdict.reason
18133
+ });
18134
+ return {
18135
+ ...result,
18136
+ completionCheck: { complete, reason: verdict.reason, parseFailed: verdict.parseFailed }
18137
+ };
18138
+ };
18139
+ let judged;
18140
+ const wrappedWait = () => {
18141
+ judged ??= compute();
18142
+ return judged;
18143
+ };
18144
+ return new Proxy(args.run, {
18145
+ get(target, prop, receiver) {
18146
+ if (prop === "wait") return wrappedWait;
18147
+ return Reflect.get(target, prop, receiver);
18148
+ }
18149
+ });
18150
+ }
18151
+
17820
18152
  // src/internal/runtime/processors/run-processors.ts
17821
18153
  var ProcessorAbort = class {
17822
18154
  constructor(processorId, reason) {
@@ -17956,6 +18288,9 @@ function wrapRunWithOutputProcessors(args) {
17956
18288
  });
17957
18289
  }
17958
18290
 
18291
+ // src/internal/runtime/local-agent/local-agent-send.ts
18292
+ init_local_agent_goal_extensions();
18293
+
17959
18294
  // src/internal/runtime/local-agent/local-agent-memory-hooks.ts
17960
18295
  var DEFAULT_MAX_RECALL_BYTES = 16e3;
17961
18296
  async function applyPreUserSendHook(args) {
@@ -18071,6 +18406,11 @@ async function executeSendLocked(inputs, message, options) {
18071
18406
  memoryFacts,
18072
18407
  activeMemorySummary
18073
18408
  );
18409
+ const projectedSystemPrompt = await projectCurrentObjective(
18410
+ inputs.storageHandle,
18411
+ options.objectiveThreadId,
18412
+ assembledSystemPrompt
18413
+ );
18074
18414
  const composedOptions = {
18075
18415
  ...options,
18076
18416
  signal: anySignal([options.signal, inputs.lifecycleAbortController.signal])
@@ -18078,7 +18418,7 @@ async function executeSendLocked(inputs, message, options) {
18078
18418
  const run = await inputs.dispatchRun(
18079
18419
  adaptedMessage,
18080
18420
  composedOptions,
18081
- assembledSystemPrompt,
18421
+ projectedSystemPrompt,
18082
18422
  memoryFacts,
18083
18423
  priorMessages,
18084
18424
  memoryTools,
@@ -18091,18 +18431,43 @@ async function executeSendLocked(inputs, message, options) {
18091
18431
  agentId: inputs.agentId,
18092
18432
  onRunEvent: options.onRunEvent
18093
18433
  }) : run;
18094
- return wrapRunWithPostReplyHook({
18434
+ const hookedRun = wrapRunWithPostReplyHook({
18095
18435
  pluginManager: inputs.pluginManagerCode,
18096
18436
  agentId: inputs.agentId,
18097
18437
  options: inputs.options,
18098
18438
  run: processedRun,
18099
18439
  userText
18100
18440
  });
18441
+ return wrapRunWithCompletionCheck({
18442
+ run: hookedRun,
18443
+ completionCheck: options.completionCheck,
18444
+ onRunEvent: options.onRunEvent,
18445
+ deps: buildCompletionCheckDeps()
18446
+ });
18447
+ }
18448
+ function buildCompletionCheckDeps() {
18449
+ return {
18450
+ judge: async (ctx, opts) => {
18451
+ const { judgeCallImpl: judgeCallImpl2 } = await Promise.resolve().then(() => (init_judge_call(), judge_call_exports));
18452
+ const { getAgentFacade: getAgentFacade2 } = await Promise.resolve().then(() => (init_agent_factory_registry(), agent_factory_registry_exports));
18453
+ return judgeCallImpl2(ctx, opts, { create: getAgentFacade2().create });
18454
+ }
18455
+ };
18101
18456
  }
18102
18457
  function readMemoryForSend(workspaceCwd, memoryConfig) {
18103
18458
  if (memoryConfig?.enabled !== true) return Promise.resolve([]);
18104
18459
  return safeCall(() => readMemoryFacts(workspaceCwd, memoryConfig), [], "memory read");
18105
18460
  }
18461
+ async function projectCurrentObjective(storageHandle, objectiveThreadId, assembled) {
18462
+ if (objectiveThreadId === void 0) return assembled;
18463
+ const objective = await safeCall(
18464
+ () => resolveCurrentObjectiveText(storageHandle, objectiveThreadId),
18465
+ void 0,
18466
+ "objective projection"
18467
+ );
18468
+ if (objective === void 0) return assembled;
18469
+ return formatObjectiveProjection(objective, assembled);
18470
+ }
18106
18471
 
18107
18472
  // src/internal/runtime/local-agent/local-agent-task-wrap.ts
18108
18473
  init_registry();
@@ -18465,20 +18830,33 @@ var LocalAgent = class {
18465
18830
  ...opts !== void 0 ? { opts } : {}
18466
18831
  });
18467
18832
  }
18833
+ // biome-ignore format: G8 budget — artifact stubs (local agents have no artifacts); kept 1-line each.
18468
18834
  listArtifacts() {
18469
18835
  return Promise.resolve([]);
18470
18836
  }
18837
+ // biome-ignore format: G8 budget — see listArtifacts.
18471
18838
  downloadArtifact(_path) {
18472
- return Promise.reject(
18473
- new exports.UnsupportedRunOperationError(
18474
- "Artifacts are not supported for local agents",
18475
- "downloadArtifact"
18476
- )
18477
- );
18839
+ return Promise.reject(new exports.UnsupportedRunOperationError("Artifacts are not supported for local agents", "downloadArtifact"));
18478
18840
  }
18479
18841
  // biome-ignore format: G8 budget — both methods delegate to `local-agent-runtime-extensions.ts`; signatures kept as 1-line each.
18480
18842
  runUntil(goal, options) {
18481
- return localAgentRunUntil(this, goal, options);
18843
+ return localAgentRunUntil(this, goal, options, { handle: this.storageHandle(), goalConfig: this.options.goal });
18844
+ }
18845
+ // biome-ignore format: SE33 G8 budget — objective methods delegate to `local-agent-goal-extensions.ts`.
18846
+ setObjective(objective, opts) {
18847
+ return localAgentSetObjective(this.storageHandle(), objective, opts);
18848
+ }
18849
+ // biome-ignore format: SE33 G8 budget — see setObjective above.
18850
+ getObjective(opts) {
18851
+ return localAgentGetObjective(this.storageHandle(), opts);
18852
+ }
18853
+ // biome-ignore format: SE33 G8 budget — see setObjective above.
18854
+ updateObjectiveOptions(opts) {
18855
+ return localAgentUpdateObjectiveOptions(this.storageHandle(), opts);
18856
+ }
18857
+ // biome-ignore format: SE33 G8 budget — see setObjective above.
18858
+ clearObjective(opts) {
18859
+ return localAgentClearObjective(this.storageHandle(), opts);
18482
18860
  }
18483
18861
  // biome-ignore format: G8 budget — see runUntil comment above.
18484
18862
  fork(options) {
@@ -19514,15 +19892,31 @@ function createSkill(spec) {
19514
19892
  // src/cron.ts
19515
19893
  init_errors();
19516
19894
 
19895
+ // src/internal/cron/fire-handler.ts
19896
+ init_registry();
19897
+
19517
19898
  // src/internal/cron/run-job.ts
19518
19899
  init_errors();
19519
19900
  init_agent_factory_registry();
19520
19901
  async function runCronJob(job) {
19521
- if (job.agent !== void 0) return runWithEphemeralAgent(job.agent, job.message);
19522
- if (job.agentId !== void 0) return runWithExistingAgent(job.agentId, job.message);
19523
- throw new exports.ConfigurationError(`Cron job ${job.id} has neither agent nor agentId \u2014 cannot run.`, {
19524
- code: "cron_no_target"
19525
- });
19902
+ if (job.workflow !== void 0) return job.workflow.run(job.inputData);
19903
+ if (job.agent !== void 0) return runWithEphemeralAgent(job.agent, requireMessage(job));
19904
+ if (job.agentId !== void 0) return runWithExistingAgent(job.agentId, requireMessage(job));
19905
+ throw new exports.ConfigurationError(
19906
+ `Cron job ${job.id} has no target (agent, agentId, or workflow) \u2014 cannot run.`,
19907
+ { code: "cron_no_target" }
19908
+ );
19909
+ }
19910
+ function isAgentRun(outcome) {
19911
+ return typeof outcome.wait === "function";
19912
+ }
19913
+ function requireMessage(job) {
19914
+ if (job.message === void 0) {
19915
+ throw new exports.ConfigurationError(`Cron job ${job.id} is an agent target but has no message.`, {
19916
+ code: "cron_missing_message"
19917
+ });
19918
+ }
19919
+ return job.message;
19526
19920
  }
19527
19921
  async function runWithExistingAgent(agentId, message) {
19528
19922
  const info = await getAgentFacade().get(agentId).catch(() => void 0);
@@ -19540,6 +19934,37 @@ async function runWithEphemeralAgent(baseOptions, message) {
19540
19934
  return agent.send(message);
19541
19935
  }
19542
19936
 
19937
+ // src/internal/cron/fire-handler.ts
19938
+ async function fireCronJobAsTask(job) {
19939
+ const fireTs = Date.now();
19940
+ const taskId = `cron-${job.id}-${fireTs}`;
19941
+ try {
19942
+ await submit({
19943
+ kind: "cron",
19944
+ id: taskId,
19945
+ allowReservedPrefix: true,
19946
+ meta: { jobId: job.id, jobName: job.name, schedule: job.cron, firedAt: fireTs },
19947
+ work: async (ctx) => {
19948
+ const outcome = await runCronJob(job);
19949
+ if (isAgentRun(outcome)) {
19950
+ ctx.signal.addEventListener("abort", () => void outcome.cancel().catch(() => {
19951
+ }), {
19952
+ once: true
19953
+ });
19954
+ const result = await outcome.wait();
19955
+ ctx.emit({ status: result.status, runId: outcome.id });
19956
+ return { status: result.status, runId: outcome.id };
19957
+ }
19958
+ ctx.emit({ status: outcome.status, runId: outcome.id });
19959
+ return { status: outcome.status, runId: outcome.id };
19960
+ }
19961
+ });
19962
+ } catch {
19963
+ const outcome = await runCronJob(job);
19964
+ if (isAgentRun(outcome)) await outcome.wait();
19965
+ }
19966
+ }
19967
+
19543
19968
  // src/internal/cron/store.ts
19544
19969
  var jobs = /* @__PURE__ */ new Map();
19545
19970
  function listJobs() {
@@ -19730,7 +20155,6 @@ function estimateNextRunAt(_cron, _timezone) {
19730
20155
  }
19731
20156
 
19732
20157
  // src/cron.ts
19733
- init_registry();
19734
20158
  var Cron = class {
19735
20159
  constructor() {
19736
20160
  }
@@ -19797,7 +20221,8 @@ var Cron = class {
19797
20221
  return updateJobStatus(jobId, false);
19798
20222
  }
19799
20223
  /**
19800
- * Manually trigger a cron job off-schedule. Returns the resulting `Run`.
20224
+ * Manually trigger a cron job off-schedule. Returns the resulting `Run`
20225
+ * (agent target) or `WorkflowRun` (workflow target — SE35).
19801
20226
  *
19802
20227
  * @public
19803
20228
  */
@@ -19814,30 +20239,7 @@ var Cron = class {
19814
20239
  * @public
19815
20240
  */
19816
20241
  static start(options = {}) {
19817
- setCronFireHandler(async (job) => {
19818
- const fireTs = Date.now();
19819
- const taskId = `cron-${job.id}-${fireTs}`;
19820
- try {
19821
- await submit({
19822
- kind: "cron",
19823
- id: taskId,
19824
- allowReservedPrefix: true,
19825
- meta: { jobId: job.id, jobName: job.name, schedule: job.cron, firedAt: fireTs },
19826
- work: async (ctx) => {
19827
- const run = await runCronJob(job);
19828
- ctx.signal.addEventListener("abort", () => void run.cancel().catch(() => {
19829
- }), {
19830
- once: true
19831
- });
19832
- const result = await run.wait();
19833
- ctx.emit({ status: result.status, runId: run.id });
19834
- return { status: result.status, runId: run.id };
19835
- }
19836
- });
19837
- } catch {
19838
- await runCronJob(job).then((run) => run.wait());
19839
- }
19840
- });
20242
+ setCronFireHandler(fireCronJobAsTask);
19841
20243
  startScheduler(options.cwd);
19842
20244
  return Promise.resolve();
19843
20245
  }
@@ -19861,17 +20263,7 @@ var Cron = class {
19861
20263
  }
19862
20264
  };
19863
20265
  async function createCronJob(options) {
19864
- if (options.agent !== void 0 && options.agentId !== void 0) {
19865
- throw new exports.ConfigurationError(
19866
- "agent and agentId are mutually exclusive \u2014 pass either agent (ephemeral) or agentId (reuse).",
19867
- { code: "cron_agent_exclusive" }
19868
- );
19869
- }
19870
- if (options.agent === void 0 && options.agentId === void 0) {
19871
- throw new exports.ConfigurationError("Cron job requires either agent or agentId", {
19872
- code: "cron_missing_agent"
19873
- });
19874
- }
20266
+ validateCronTarget(options);
19875
20267
  validateCronExpression(options.cron);
19876
20268
  const timezone = options.timezone ?? "UTC";
19877
20269
  validateTimezone(timezone);
@@ -19882,20 +20274,51 @@ async function createCronJob(options) {
19882
20274
  id: generateCronId(),
19883
20275
  cron: options.cron,
19884
20276
  timezone,
19885
- message: options.message,
19886
20277
  enabled: options.enabled ?? true,
19887
20278
  status: options.enabled === false ? "paused" : "scheduled",
19888
20279
  runtime,
19889
20280
  createdAt: now,
19890
20281
  nextRunAt: estimateNextRunAt(options.cron),
19891
20282
  ...options.name !== void 0 ? { name: options.name } : {},
20283
+ ...options.message !== void 0 ? { message: options.message } : {},
19892
20284
  ...options.agent !== void 0 ? { agent: options.agent } : {},
19893
- ...options.agentId !== void 0 ? { agentId: options.agentId } : {}
20285
+ ...options.agentId !== void 0 ? { agentId: options.agentId } : {},
20286
+ ...options.workflow !== void 0 ? { workflow: options.workflow } : {},
20287
+ ...options.inputData !== void 0 ? { inputData: options.inputData } : {}
19894
20288
  };
19895
20289
  upsertJob(job);
19896
20290
  return job;
19897
20291
  }
20292
+ function validateCronTarget(options) {
20293
+ const targets = [options.agent, options.agentId, options.workflow].filter(
20294
+ (t) => t !== void 0
20295
+ ).length;
20296
+ if (targets > 1) {
20297
+ throw new exports.ConfigurationError(
20298
+ "agent, agentId, and workflow are mutually exclusive \u2014 pass exactly one target.",
20299
+ { code: "cron_ambiguous_target" }
20300
+ );
20301
+ }
20302
+ if (targets === 0) {
20303
+ throw new exports.ConfigurationError(
20304
+ "Cron job requires exactly one target: agent, agentId, or workflow.",
20305
+ { code: "cron_no_target" }
20306
+ );
20307
+ }
20308
+ if (options.workflow !== void 0 && options.message !== void 0) {
20309
+ throw new exports.ConfigurationError(
20310
+ "A workflow cron target takes inputData, not a message \u2014 remove `message`.",
20311
+ { code: "cron_workflow_message" }
20312
+ );
20313
+ }
20314
+ if (options.workflow === void 0 && options.message === void 0) {
20315
+ throw new exports.ConfigurationError("An agent cron target requires a message.", {
20316
+ code: "cron_missing_message"
20317
+ });
20318
+ }
20319
+ }
19898
20320
  function detectRuntime(options) {
20321
+ if (options.workflow !== void 0) return "local";
19899
20322
  if (options.agentId !== void 0) {
19900
20323
  return options.agentId.startsWith("bc-") ? "cloud" : "local";
19901
20324
  }
@@ -20062,12 +20485,17 @@ var EventBus = class {
20062
20485
 
20063
20486
  // src/index.ts
20064
20487
  init_generate_object();
20488
+ init_conversation_storage_fs();
20065
20489
 
20066
20490
  // src/internal/persistence/conversation-storage-memory.ts
20491
+ init_pagination();
20492
+ init_session_meta();
20067
20493
  var InMemoryConversationStorage = class {
20068
20494
  #store = /* @__PURE__ */ new Map();
20069
20495
  // SE4 — session-level metadata (title/tag), kept beside the transcript.
20070
20496
  #meta = /* @__PURE__ */ new Map();
20497
+ // SE33 — durable thread-scoped objective, kept beside the transcript.
20498
+ #objectives = /* @__PURE__ */ new Map();
20071
20499
  async getMessages(conversationId, opts) {
20072
20500
  const existing = this.#store.get(conversationId);
20073
20501
  return existing === void 0 ? [] : paginate(existing.slice(), opts);
@@ -20097,18 +20525,21 @@ var InMemoryConversationStorage = class {
20097
20525
  async deleteConversation(conversationId) {
20098
20526
  this.#store.delete(conversationId);
20099
20527
  this.#meta.delete(conversationId);
20528
+ this.#objectives.delete(conversationId);
20100
20529
  }
20101
20530
  async deleteScope(prefix) {
20102
20531
  let n = 0;
20103
- for (const id of [...this.#store.keys()]) {
20104
- if (id.startsWith(prefix)) {
20105
- this.#store.delete(id);
20106
- this.#meta.delete(id);
20107
- n += 1;
20108
- }
20109
- }
20110
- for (const id of [...this.#meta.keys()]) {
20111
- if (id.startsWith(prefix)) this.#meta.delete(id);
20532
+ const ids = /* @__PURE__ */ new Set([
20533
+ ...this.#store.keys(),
20534
+ ...this.#meta.keys(),
20535
+ ...this.#objectives.keys()
20536
+ ]);
20537
+ for (const id of ids) {
20538
+ if (!id.startsWith(prefix)) continue;
20539
+ this.#store.delete(id);
20540
+ this.#meta.delete(id);
20541
+ this.#objectives.delete(id);
20542
+ n += 1;
20112
20543
  }
20113
20544
  return n;
20114
20545
  }
@@ -20125,9 +20556,27 @@ var InMemoryConversationStorage = class {
20125
20556
  const current = this.#meta.get(conversationId) ?? {};
20126
20557
  this.#meta.set(conversationId, applyMetaPatch(current, patch));
20127
20558
  }
20559
+ async getObjectiveRecord(conversationId) {
20560
+ const rec = this.#objectives.get(conversationId);
20561
+ return rec === void 0 ? void 0 : { ...rec };
20562
+ }
20563
+ async setObjectiveRecord(conversationId, record) {
20564
+ if (record === null) this.#objectives.delete(conversationId);
20565
+ else this.#objectives.set(conversationId, { ...record });
20566
+ }
20567
+ // SE33 (HIGH-1 fix) — atomic in the single-threaded runtime: the get→mutate→set
20568
+ // runs synchronously, so there is no interleaving window for a concurrent frame.
20569
+ async updateObjectiveRecord(conversationId, mutate) {
20570
+ const current = this.#objectives.get(conversationId);
20571
+ const next = mutate(current === void 0 ? void 0 : { ...current });
20572
+ if (next === void 0) return;
20573
+ if (next === null) this.#objectives.delete(conversationId);
20574
+ else this.#objectives.set(conversationId, { ...next });
20575
+ }
20128
20576
  async dispose() {
20129
20577
  this.#store.clear();
20130
20578
  this.#meta.clear();
20579
+ this.#objectives.clear();
20131
20580
  }
20132
20581
  };
20133
20582
 
@@ -21950,7 +22399,6 @@ exports.AgentBuilder = AgentBuilder;
21950
22399
  exports.Budget = Budget;
21951
22400
  exports.Cron = Cron;
21952
22401
  exports.EventBus = EventBus;
21953
- exports.FileSystemConversationStorage = FileSystemConversationStorage;
21954
22402
  exports.InMemoryConversationStorage = InMemoryConversationStorage;
21955
22403
  exports.JobQueue = JobQueue;
21956
22404
  exports.Memory = Memory;