@theokit/sdk 2.27.0 → 2.29.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +34 -0
- package/dist/a2a/index.cjs +373 -17
- package/dist/a2a/index.cjs.map +1 -1
- package/dist/a2a/index.js +373 -17
- package/dist/a2a/index.js.map +1 -1
- package/dist/{cron-BR1NCSk1.d.cts → cron-COSAehOL.d.ts} +110 -3
- package/dist/{cron-DgEQCJ2i.d.ts → cron-yJoNUZxe.d.cts} +110 -3
- package/dist/cron.cjs +914 -508
- package/dist/cron.cjs.map +1 -1
- package/dist/cron.d.cts +2 -2
- package/dist/cron.d.ts +2 -2
- package/dist/cron.js +915 -509
- package/dist/cron.js.map +1 -1
- package/dist/{errors-CbY3pxY7.d.ts → errors-BxMIlgLP.d.ts} +1 -1
- package/dist/{errors-DLMNb4Ka.d.cts → errors-tP-8O-hR.d.cts} +1 -1
- package/dist/errors.d.cts +2 -2
- package/dist/eval.cjs +914 -508
- package/dist/eval.cjs.map +1 -1
- package/dist/eval.js +915 -509
- package/dist/eval.js.map +1 -1
- package/dist/filesystem/index.cjs +304 -0
- package/dist/filesystem/index.cjs.map +1 -0
- package/dist/filesystem/index.d.cts +12 -0
- package/dist/filesystem/index.d.ts +12 -0
- package/dist/filesystem/index.js +295 -0
- package/dist/filesystem/index.js.map +1 -0
- package/dist/filesystem/local-filesystem.d.cts +36 -0
- package/dist/filesystem/local-filesystem.d.ts +36 -0
- package/dist/filesystem/types.d.cts +118 -0
- package/dist/filesystem/types.d.ts +118 -0
- package/dist/index.cjs +973 -570
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +13 -7
- package/dist/index.d.ts +13 -7
- package/dist/index.js +972 -568
- package/dist/index.js.map +1 -1
- package/dist/internal/persistence/conversation-storage-fs.d.cts +4 -0
- package/dist/internal/persistence/conversation-storage-fs.d.ts +4 -0
- package/dist/internal/persistence/conversation-storage-memory.d.cts +4 -0
- package/dist/internal/persistence/conversation-storage-memory.d.ts +4 -0
- package/dist/internal/persistence/objective-coerce.d.cts +9 -0
- package/dist/internal/persistence/objective-coerce.d.ts +9 -0
- package/dist/internal/runtime/lifecycle/wrap-completion-check-run.d.ts +30 -0
- package/dist/internal/runtime/local-agent/local-agent-goal-extensions.d.ts +80 -0
- package/dist/internal/runtime/objective/objective-store.d.ts +33 -0
- package/dist/{run-CdWiihyU.d.cts → run-q_P0vHlY.d.cts} +71 -2
- package/dist/{run-CdWiihyU.d.ts → run-q_P0vHlY.d.ts} +71 -2
- package/dist/types/agent.d.ts +32 -1
- package/dist/types/conversation-storage.d.ts +23 -0
- package/dist/types/goal-events.d.ts +7 -0
- package/dist/types/index.d.ts +1 -0
- package/dist/types/objective.d.ts +45 -0
- package/dist/types/run-events.d.ts +12 -1
- package/dist/types/run.d.ts +58 -0
- package/package.json +11 -1
package/dist/index.js
CHANGED
|
@@ -2,8 +2,8 @@ import { z, toJSONSchema } from 'zod';
|
|
|
2
2
|
import { createRequire } from 'module';
|
|
3
3
|
import { createHash, randomUUID, randomBytes } from 'crypto';
|
|
4
4
|
import { existsSync, rmSync, mkdirSync, renameSync, readFileSync, realpathSync, lstatSync, readlinkSync, readdirSync } from 'fs';
|
|
5
|
-
import { join, dirname,
|
|
6
|
-
import {
|
|
5
|
+
import { join, dirname, resolve, sep, relative, isAbsolute } from 'path';
|
|
6
|
+
import { rm, readdir, readFile, mkdir, stat, appendFile, open, rename, unlink, statfs, access, writeFile } from 'fs/promises';
|
|
7
7
|
import { AsyncLocalStorage } from 'async_hooks';
|
|
8
8
|
import { homedir } from 'os';
|
|
9
9
|
import { spawn } from 'child_process';
|
|
@@ -913,7 +913,7 @@ function safeFilenameForId(id, options) {
|
|
|
913
913
|
code: "invalid_filename_id"
|
|
914
914
|
});
|
|
915
915
|
}
|
|
916
|
-
const maxLen = options?.maxLen;
|
|
916
|
+
const maxLen = options?.maxLen ?? 128;
|
|
917
917
|
const lower = id.toLowerCase();
|
|
918
918
|
if (lower.length <= maxLen && IDENTIFIER_PATTERN.test(lower)) {
|
|
919
919
|
return lower;
|
|
@@ -1110,6 +1110,72 @@ var init_markdown_store = __esm({
|
|
|
1110
1110
|
}
|
|
1111
1111
|
});
|
|
1112
1112
|
|
|
1113
|
+
// src/internal/persistence/file-lock.ts
|
|
1114
|
+
async function getProperLockfile() {
|
|
1115
|
+
if (cached !== void 0) return cached;
|
|
1116
|
+
try {
|
|
1117
|
+
const mod = await import('proper-lockfile');
|
|
1118
|
+
if (!validateLockModule(mod)) {
|
|
1119
|
+
if (!warnedStructural) {
|
|
1120
|
+
warnedStructural = true;
|
|
1121
|
+
process.stderr.write(
|
|
1122
|
+
"[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"
|
|
1123
|
+
);
|
|
1124
|
+
}
|
|
1125
|
+
cached = null;
|
|
1126
|
+
return cached;
|
|
1127
|
+
}
|
|
1128
|
+
cached = mod;
|
|
1129
|
+
} catch {
|
|
1130
|
+
cached = null;
|
|
1131
|
+
}
|
|
1132
|
+
return cached;
|
|
1133
|
+
}
|
|
1134
|
+
function validateLockModule(mod) {
|
|
1135
|
+
if (mod === null || mod === void 0 || typeof mod !== "object") return false;
|
|
1136
|
+
const m = mod;
|
|
1137
|
+
return typeof m.lock === "function" && typeof m.unlock === "function";
|
|
1138
|
+
}
|
|
1139
|
+
async function withFileLock(path, fn, options) {
|
|
1140
|
+
const lib = await getProperLockfile();
|
|
1141
|
+
if (lib === null) {
|
|
1142
|
+
if (!warnedMissing) {
|
|
1143
|
+
warnedMissing = true;
|
|
1144
|
+
process.stderr.write(
|
|
1145
|
+
"[theokit-sdk] proper-lockfile not installed; cross-process file lock unavailable. Install with: pnpm add proper-lockfile\n"
|
|
1146
|
+
);
|
|
1147
|
+
}
|
|
1148
|
+
return withCwdMutex(`file-lock:${path}`, fn);
|
|
1149
|
+
}
|
|
1150
|
+
return withCwdMutex(`file-lock:${path}`, async () => {
|
|
1151
|
+
const release = await lib.lock(path, {
|
|
1152
|
+
// EC-1: companion lockfile, target path may not exist yet.
|
|
1153
|
+
lockfilePath: `${path}.lock`,
|
|
1154
|
+
realpath: false,
|
|
1155
|
+
stale: 3e4,
|
|
1156
|
+
retries: {
|
|
1157
|
+
retries: 5,
|
|
1158
|
+
factor: 1.5,
|
|
1159
|
+
minTimeout: 100,
|
|
1160
|
+
maxTimeout: 5e3
|
|
1161
|
+
}
|
|
1162
|
+
});
|
|
1163
|
+
try {
|
|
1164
|
+
return await fn();
|
|
1165
|
+
} finally {
|
|
1166
|
+
await release();
|
|
1167
|
+
}
|
|
1168
|
+
});
|
|
1169
|
+
}
|
|
1170
|
+
var cached, warnedMissing, warnedStructural;
|
|
1171
|
+
var init_file_lock = __esm({
|
|
1172
|
+
"src/internal/persistence/file-lock.ts"() {
|
|
1173
|
+
init_cwd_mutex();
|
|
1174
|
+
warnedMissing = false;
|
|
1175
|
+
warnedStructural = false;
|
|
1176
|
+
}
|
|
1177
|
+
});
|
|
1178
|
+
|
|
1113
1179
|
// src/internal/runtime/context/yaml-frontmatter.ts
|
|
1114
1180
|
function parseSimpleYaml(text) {
|
|
1115
1181
|
const fields = {};
|
|
@@ -1364,94 +1430,459 @@ var init_session_summary_writer = __esm({
|
|
|
1364
1430
|
MAX_TURN_CHARS = 2e3;
|
|
1365
1431
|
}
|
|
1366
1432
|
});
|
|
1367
|
-
|
|
1368
|
-
|
|
1369
|
-
|
|
1370
|
-
function currentToolWhitelist() {
|
|
1371
|
-
return toolWhitelistStore.getStore();
|
|
1433
|
+
function sessionFilePath(cwd, agentId) {
|
|
1434
|
+
const safe2 = sanitizeIdentifier(agentId, { maxLen: 128 });
|
|
1435
|
+
return safePathJoin(cwd, ".theokit", "agents", safe2, "messages.jsonl");
|
|
1372
1436
|
}
|
|
1373
|
-
function
|
|
1374
|
-
const
|
|
1375
|
-
|
|
1376
|
-
|
|
1377
|
-
return
|
|
1378
|
-
|
|
1379
|
-
|
|
1380
|
-
};
|
|
1437
|
+
async function readJsonlLines(cwd, agentId) {
|
|
1438
|
+
const path = sessionFilePath(cwd, agentId);
|
|
1439
|
+
try {
|
|
1440
|
+
const raw = await readFile(path, "utf8");
|
|
1441
|
+
return raw.split("\n").filter((line) => line.length > 0);
|
|
1442
|
+
} catch {
|
|
1443
|
+
return [];
|
|
1381
1444
|
}
|
|
1382
|
-
return { allowed: true };
|
|
1383
1445
|
}
|
|
1384
|
-
|
|
1385
|
-
|
|
1386
|
-
|
|
1387
|
-
|
|
1446
|
+
function warnMalformed(agentId, line) {
|
|
1447
|
+
process.stderr.write(
|
|
1448
|
+
`[theokit-sdk] skipping malformed line in messages.jsonl (${agentId}): ${line.slice(0, 80)}...
|
|
1449
|
+
`
|
|
1450
|
+
);
|
|
1451
|
+
}
|
|
1452
|
+
function hydrateSessionLine(parsed) {
|
|
1453
|
+
if (typeof parsed.text !== "string" || parsed.role === void 0) return void 0;
|
|
1454
|
+
if (parsed.role === "user" || parsed.role === "assistant") {
|
|
1455
|
+
return { role: parsed.role, text: parsed.text };
|
|
1388
1456
|
}
|
|
1389
|
-
|
|
1390
|
-
|
|
1391
|
-
|
|
1392
|
-
function createSemaphore(permits) {
|
|
1393
|
-
if (!Number.isInteger(permits) || permits < 1) {
|
|
1394
|
-
throw new ConfigurationError(
|
|
1395
|
-
`async-semaphore: permits must be a positive integer, got ${permits}`,
|
|
1396
|
-
{ code: "invalid_concurrency" }
|
|
1397
|
-
);
|
|
1457
|
+
if (parsed.role === "tool_call" || parsed.role === "tool_result") {
|
|
1458
|
+
const label = parsed.role === "tool_call" ? "tool call" : "tool result";
|
|
1459
|
+
return { role: "assistant", text: `[${label}] ${parsed.text}` };
|
|
1398
1460
|
}
|
|
1399
|
-
|
|
1400
|
-
|
|
1401
|
-
|
|
1402
|
-
|
|
1403
|
-
|
|
1404
|
-
|
|
1405
|
-
|
|
1406
|
-
|
|
1407
|
-
|
|
1461
|
+
return void 0;
|
|
1462
|
+
}
|
|
1463
|
+
async function readSessionFile(cwd, agentId) {
|
|
1464
|
+
const lines = await readJsonlLines(cwd, agentId);
|
|
1465
|
+
const messages = [];
|
|
1466
|
+
for (const line of lines) {
|
|
1467
|
+
try {
|
|
1468
|
+
const msg = hydrateSessionLine(JSON.parse(line));
|
|
1469
|
+
if (msg !== void 0) messages.push(msg);
|
|
1470
|
+
} catch {
|
|
1471
|
+
warnMalformed(agentId, line);
|
|
1408
1472
|
}
|
|
1409
1473
|
}
|
|
1410
|
-
return
|
|
1411
|
-
|
|
1412
|
-
|
|
1413
|
-
|
|
1414
|
-
|
|
1415
|
-
|
|
1416
|
-
|
|
1417
|
-
|
|
1418
|
-
|
|
1419
|
-
|
|
1420
|
-
|
|
1421
|
-
|
|
1422
|
-
|
|
1423
|
-
|
|
1424
|
-
}
|
|
1474
|
+
return messages;
|
|
1475
|
+
}
|
|
1476
|
+
async function readAllPersistedMessages(cwd, agentId) {
|
|
1477
|
+
const lines = await readJsonlLines(cwd, agentId);
|
|
1478
|
+
const messages = [];
|
|
1479
|
+
for (const line of lines) {
|
|
1480
|
+
try {
|
|
1481
|
+
const parsed = JSON.parse(line);
|
|
1482
|
+
if (parsed.role !== void 0 && VALID_ROLES.has(parsed.role) && typeof parsed.text === "string") {
|
|
1483
|
+
messages.push({
|
|
1484
|
+
role: parsed.role,
|
|
1485
|
+
text: parsed.text,
|
|
1486
|
+
at: typeof parsed.at === "number" ? parsed.at : Date.now()
|
|
1487
|
+
});
|
|
1488
|
+
}
|
|
1489
|
+
} catch {
|
|
1490
|
+
warnMalformed(agentId, line);
|
|
1425
1491
|
}
|
|
1492
|
+
}
|
|
1493
|
+
return messages;
|
|
1494
|
+
}
|
|
1495
|
+
async function appendAnyPersistedMessage(cwd, agentId, record) {
|
|
1496
|
+
await appendPersistedMessages(cwd, agentId, [record]);
|
|
1497
|
+
}
|
|
1498
|
+
async function appendPersistedMessages(cwd, agentId, records) {
|
|
1499
|
+
if (records.length === 0) return;
|
|
1500
|
+
const path = sessionFilePath(cwd, agentId);
|
|
1501
|
+
const payload = records.map((r) => `${redactSecrets(JSON.stringify(r))}
|
|
1502
|
+
`).join("");
|
|
1503
|
+
const dir = dirname(path);
|
|
1504
|
+
let written = false;
|
|
1505
|
+
const attempt = async () => {
|
|
1506
|
+
await mkdir(dir, { recursive: true });
|
|
1507
|
+
await withFileLock(path, async () => {
|
|
1508
|
+
await appendFile(path, payload, "utf8");
|
|
1509
|
+
written = true;
|
|
1510
|
+
});
|
|
1426
1511
|
};
|
|
1512
|
+
try {
|
|
1513
|
+
await attempt();
|
|
1514
|
+
} catch (cause) {
|
|
1515
|
+
if (written || cause.code !== "ENOENT") throw cause;
|
|
1516
|
+
await attempt();
|
|
1517
|
+
}
|
|
1427
1518
|
}
|
|
1428
|
-
|
|
1429
|
-
|
|
1430
|
-
|
|
1519
|
+
async function rewriteLockedSession(path, transform) {
|
|
1520
|
+
await withFileLock(path, async () => {
|
|
1521
|
+
let raw;
|
|
1522
|
+
try {
|
|
1523
|
+
raw = await readFile(path, "utf8");
|
|
1524
|
+
} catch {
|
|
1525
|
+
return;
|
|
1526
|
+
}
|
|
1527
|
+
const lines = raw.split("\n").filter((line) => line.length > 0);
|
|
1528
|
+
const next = transform(lines);
|
|
1529
|
+
if (next === void 0) return;
|
|
1530
|
+
await replaceFileAtomic(path, next);
|
|
1531
|
+
});
|
|
1532
|
+
}
|
|
1533
|
+
async function compactSessionFile(cwd, agentId, maxTurns) {
|
|
1534
|
+
const path = sessionFilePath(cwd, agentId);
|
|
1535
|
+
if (!existsSync(path)) return;
|
|
1536
|
+
await rewriteLockedSession(
|
|
1537
|
+
path,
|
|
1538
|
+
(lines) => lines.length <= maxTurns * 2 ? void 0 : `${lines.slice(-maxTurns).join("\n")}
|
|
1539
|
+
`
|
|
1540
|
+
);
|
|
1541
|
+
}
|
|
1542
|
+
async function truncateSessionTo(cwd, agentId, keepCount) {
|
|
1543
|
+
const path = sessionFilePath(cwd, agentId);
|
|
1544
|
+
if (!existsSync(path)) return 0;
|
|
1545
|
+
let kept = 0;
|
|
1546
|
+
await rewriteLockedSession(path, (lines) => {
|
|
1547
|
+
const keep = Math.max(0, Math.min(keepCount, lines.length));
|
|
1548
|
+
kept = keep;
|
|
1549
|
+
if (keep === lines.length) return void 0;
|
|
1550
|
+
return keep === 0 ? "" : `${lines.slice(0, keep).join("\n")}
|
|
1551
|
+
`;
|
|
1552
|
+
});
|
|
1553
|
+
return kept;
|
|
1554
|
+
}
|
|
1555
|
+
var VALID_ROLES;
|
|
1556
|
+
var init_agent_session_store = __esm({
|
|
1557
|
+
"src/internal/runtime/session/agent-session-store.ts"() {
|
|
1558
|
+
init_atomic_write();
|
|
1559
|
+
init_file_lock();
|
|
1560
|
+
init_security();
|
|
1561
|
+
VALID_ROLES = /* @__PURE__ */ new Set([
|
|
1562
|
+
"user",
|
|
1563
|
+
"assistant",
|
|
1564
|
+
"system",
|
|
1565
|
+
"tool_call",
|
|
1566
|
+
"tool_result"
|
|
1567
|
+
]);
|
|
1431
1568
|
}
|
|
1432
1569
|
});
|
|
1433
1570
|
|
|
1434
|
-
// src/internal/
|
|
1435
|
-
|
|
1436
|
-
|
|
1437
|
-
|
|
1438
|
-
|
|
1439
|
-
|
|
1440
|
-
|
|
1441
|
-
|
|
1442
|
-
|
|
1443
|
-
|
|
1444
|
-
|
|
1445
|
-
|
|
1446
|
-
|
|
1571
|
+
// src/internal/persistence/objective-coerce.ts
|
|
1572
|
+
function coerceOptions(raw) {
|
|
1573
|
+
if (typeof raw !== "object" || raw === null) return void 0;
|
|
1574
|
+
const o = raw;
|
|
1575
|
+
const out = {};
|
|
1576
|
+
if (typeof o.maxRuns === "number") out.maxRuns = o.maxRuns;
|
|
1577
|
+
if (typeof o.judgeModel === "string") out.judgeModel = o.judgeModel;
|
|
1578
|
+
if (typeof o.prompt === "string") out.prompt = o.prompt;
|
|
1579
|
+
return Object.keys(out).length > 0 ? out : void 0;
|
|
1580
|
+
}
|
|
1581
|
+
function coerceObjectiveRecord(raw) {
|
|
1582
|
+
if (typeof raw !== "object" || raw === null) return void 0;
|
|
1583
|
+
const o = raw;
|
|
1584
|
+
if (o._schemaVersion !== 1) return void 0;
|
|
1585
|
+
if (typeof o.objective !== "string") return void 0;
|
|
1586
|
+
if (typeof o.runsUsed !== "number") return void 0;
|
|
1587
|
+
if (typeof o.status !== "string" || !STATUSES.includes(o.status))
|
|
1588
|
+
return void 0;
|
|
1589
|
+
const options = coerceOptions(o.options);
|
|
1590
|
+
return {
|
|
1591
|
+
_schemaVersion: 1,
|
|
1592
|
+
objective: o.objective,
|
|
1593
|
+
...options !== void 0 ? { options } : {},
|
|
1594
|
+
status: o.status,
|
|
1595
|
+
runsUsed: o.runsUsed
|
|
1596
|
+
};
|
|
1597
|
+
}
|
|
1598
|
+
var STATUSES;
|
|
1599
|
+
var init_objective_coerce = __esm({
|
|
1600
|
+
"src/internal/persistence/objective-coerce.ts"() {
|
|
1601
|
+
STATUSES = ["active", "done", "paused"];
|
|
1447
1602
|
}
|
|
1448
1603
|
});
|
|
1449
1604
|
|
|
1450
|
-
// src/internal/
|
|
1451
|
-
function
|
|
1452
|
-
|
|
1453
|
-
const
|
|
1454
|
-
const
|
|
1605
|
+
// src/internal/persistence/pagination.ts
|
|
1606
|
+
function paginate(items, opts) {
|
|
1607
|
+
if (opts === void 0 || opts.offset === void 0 && opts.limit === void 0) return items;
|
|
1608
|
+
const start = Math.max(0, opts.offset ?? 0);
|
|
1609
|
+
const end = opts.limit === void 0 ? items.length : start + Math.max(0, opts.limit);
|
|
1610
|
+
return items.slice(start, end);
|
|
1611
|
+
}
|
|
1612
|
+
var init_pagination = __esm({
|
|
1613
|
+
"src/internal/persistence/pagination.ts"() {
|
|
1614
|
+
}
|
|
1615
|
+
});
|
|
1616
|
+
|
|
1617
|
+
// src/internal/persistence/session-meta.ts
|
|
1618
|
+
function applyMetaPatch(current, patch) {
|
|
1619
|
+
const next = {};
|
|
1620
|
+
if (current.title !== void 0) next.title = current.title;
|
|
1621
|
+
if (current.tag !== void 0) next.tag = current.tag;
|
|
1622
|
+
if (patch.title === null) delete next.title;
|
|
1623
|
+
else if (patch.title !== void 0) next.title = patch.title;
|
|
1624
|
+
if (patch.tag === null) delete next.tag;
|
|
1625
|
+
else if (patch.tag !== void 0) next.tag = patch.tag;
|
|
1626
|
+
return next;
|
|
1627
|
+
}
|
|
1628
|
+
function coerceSessionMeta(raw) {
|
|
1629
|
+
if (typeof raw !== "object" || raw === null) return void 0;
|
|
1630
|
+
const obj = raw;
|
|
1631
|
+
const meta = {};
|
|
1632
|
+
if (typeof obj.title === "string") meta.title = obj.title;
|
|
1633
|
+
if (typeof obj.tag === "string") meta.tag = obj.tag;
|
|
1634
|
+
return meta.title === void 0 && meta.tag === void 0 ? void 0 : meta;
|
|
1635
|
+
}
|
|
1636
|
+
var init_session_meta = __esm({
|
|
1637
|
+
"src/internal/persistence/session-meta.ts"() {
|
|
1638
|
+
}
|
|
1639
|
+
});
|
|
1640
|
+
function toStoredMessage(record) {
|
|
1641
|
+
return {
|
|
1642
|
+
role: record.role,
|
|
1643
|
+
content: record.text,
|
|
1644
|
+
at: record.at
|
|
1645
|
+
};
|
|
1646
|
+
}
|
|
1647
|
+
function toRecord(message) {
|
|
1648
|
+
return { role: message.role, text: message.content, at: message.at ?? Date.now() };
|
|
1649
|
+
}
|
|
1650
|
+
var FileSystemConversationStorage;
|
|
1651
|
+
var init_conversation_storage_fs = __esm({
|
|
1652
|
+
"src/internal/persistence/conversation-storage-fs.ts"() {
|
|
1653
|
+
init_agent_session_store();
|
|
1654
|
+
init_security();
|
|
1655
|
+
init_path_guard();
|
|
1656
|
+
init_file_lock();
|
|
1657
|
+
init_objective_coerce();
|
|
1658
|
+
init_pagination();
|
|
1659
|
+
init_session_meta();
|
|
1660
|
+
FileSystemConversationStorage = class {
|
|
1661
|
+
#root;
|
|
1662
|
+
constructor(opts = {}) {
|
|
1663
|
+
this.#root = opts.root ?? process.cwd();
|
|
1664
|
+
}
|
|
1665
|
+
/** Exposed for tests + diagnostics. The path is sanitized at use sites. */
|
|
1666
|
+
get root() {
|
|
1667
|
+
return this.#root;
|
|
1668
|
+
}
|
|
1669
|
+
async getMessages(conversationId, opts) {
|
|
1670
|
+
const records = await readAllPersistedMessages(this.#root, conversationId);
|
|
1671
|
+
const all = records.map(toStoredMessage);
|
|
1672
|
+
return paginate(all, opts);
|
|
1673
|
+
}
|
|
1674
|
+
async appendMessage(conversationId, message) {
|
|
1675
|
+
await appendAnyPersistedMessage(this.#root, conversationId, toRecord(message));
|
|
1676
|
+
}
|
|
1677
|
+
async appendMessages(conversationId, messages) {
|
|
1678
|
+
await appendPersistedMessages(this.#root, conversationId, messages.map(toRecord));
|
|
1679
|
+
}
|
|
1680
|
+
async truncateConversation(conversationId, keepCount) {
|
|
1681
|
+
return truncateSessionTo(this.#root, conversationId, keepCount);
|
|
1682
|
+
}
|
|
1683
|
+
async deleteConversation(conversationId) {
|
|
1684
|
+
const safe2 = sanitizeIdentifier(conversationId, { maxLen: 128 });
|
|
1685
|
+
const dirPath = safePathJoin(this.#root, ".theokit", "agents", safe2);
|
|
1686
|
+
await rm(dirPath, { recursive: true, force: true });
|
|
1687
|
+
}
|
|
1688
|
+
async deleteScope(prefix) {
|
|
1689
|
+
const ids = await this.listConversationIds();
|
|
1690
|
+
const matching = ids.filter((id) => id.startsWith(prefix));
|
|
1691
|
+
for (const id of matching) await this.deleteConversation(id);
|
|
1692
|
+
return matching.length;
|
|
1693
|
+
}
|
|
1694
|
+
async listConversationIds(opts = {}) {
|
|
1695
|
+
const agentsRoot = safePathJoin(this.#root, ".theokit", "agents");
|
|
1696
|
+
let entries;
|
|
1697
|
+
try {
|
|
1698
|
+
entries = await readdir(agentsRoot);
|
|
1699
|
+
} catch (cause) {
|
|
1700
|
+
if (cause.code === "ENOENT") return [];
|
|
1701
|
+
throw cause;
|
|
1702
|
+
}
|
|
1703
|
+
if (opts.limit !== void 0) return entries.slice(0, opts.limit);
|
|
1704
|
+
return entries;
|
|
1705
|
+
}
|
|
1706
|
+
async compact(conversationId, maxTurns) {
|
|
1707
|
+
await compactSessionFile(this.#root, conversationId, maxTurns);
|
|
1708
|
+
}
|
|
1709
|
+
// SE4 — session metadata persisted as a per-conversation sidecar
|
|
1710
|
+
// `<root>/.theokit/agents/<safeId>/session.json` (same sanitized perimeter as
|
|
1711
|
+
// the transcript). Kept separate from messages.jsonl so a title/tag write does
|
|
1712
|
+
// not rewrite the append-only log.
|
|
1713
|
+
#metaPath(conversationId) {
|
|
1714
|
+
const safe2 = sanitizeIdentifier(conversationId, { maxLen: 128 });
|
|
1715
|
+
return safePathJoin(this.#root, ".theokit", "agents", safe2, "session.json");
|
|
1716
|
+
}
|
|
1717
|
+
async getSessionMeta(conversationId) {
|
|
1718
|
+
try {
|
|
1719
|
+
const raw = await readFile(this.#metaPath(conversationId), "utf8");
|
|
1720
|
+
return coerceSessionMeta(JSON.parse(raw));
|
|
1721
|
+
} catch (cause) {
|
|
1722
|
+
if (cause.code === "ENOENT") return void 0;
|
|
1723
|
+
throw cause;
|
|
1724
|
+
}
|
|
1725
|
+
}
|
|
1726
|
+
async setSessionMeta(conversationId, patch) {
|
|
1727
|
+
const metaPath = this.#metaPath(conversationId);
|
|
1728
|
+
await mkdir(dirname(metaPath), { recursive: true });
|
|
1729
|
+
await withFileLock(metaPath, async () => {
|
|
1730
|
+
let current = {};
|
|
1731
|
+
try {
|
|
1732
|
+
current = coerceSessionMeta(JSON.parse(await readFile(metaPath, "utf8"))) ?? {};
|
|
1733
|
+
} catch (cause) {
|
|
1734
|
+
if (cause.code !== "ENOENT") throw cause;
|
|
1735
|
+
}
|
|
1736
|
+
const next = applyMetaPatch(current, patch);
|
|
1737
|
+
await writeFile(metaPath, redactSecrets(JSON.stringify(next)), "utf8");
|
|
1738
|
+
});
|
|
1739
|
+
}
|
|
1740
|
+
// SE33 — the durable objective is kept in `objective.json` beside the
|
|
1741
|
+
// transcript (separate from messages.jsonl + session.json). Uses the TOTAL
|
|
1742
|
+
// `safeFilenameForId` (not `sanitizeIdentifier`) so a caller-supplied
|
|
1743
|
+
// `threadId` with exotic characters (e.g. "user@example.com") hashes to a
|
|
1744
|
+
// deterministic dir instead of throwing — honoring the objective methods'
|
|
1745
|
+
// never-throw contract (ADR D6). Conforming ids pass through unchanged, so
|
|
1746
|
+
// the objective still sits beside the transcript for the normal case.
|
|
1747
|
+
#objectivePath(conversationId) {
|
|
1748
|
+
const safe2 = safeFilenameForId(conversationId, { maxLen: 128 });
|
|
1749
|
+
return safePathJoin(this.#root, ".theokit", "agents", safe2, "objective.json");
|
|
1750
|
+
}
|
|
1751
|
+
async getObjectiveRecord(conversationId) {
|
|
1752
|
+
try {
|
|
1753
|
+
const raw = await readFile(this.#objectivePath(conversationId), "utf8");
|
|
1754
|
+
return coerceObjectiveRecord(JSON.parse(raw));
|
|
1755
|
+
} catch (cause) {
|
|
1756
|
+
if (cause.code === "ENOENT") return void 0;
|
|
1757
|
+
throw cause;
|
|
1758
|
+
}
|
|
1759
|
+
}
|
|
1760
|
+
async setObjectiveRecord(conversationId, record) {
|
|
1761
|
+
const path = this.#objectivePath(conversationId);
|
|
1762
|
+
if (record === null) {
|
|
1763
|
+
await rm(path, { force: true });
|
|
1764
|
+
return;
|
|
1765
|
+
}
|
|
1766
|
+
await mkdir(dirname(path), { recursive: true });
|
|
1767
|
+
await withFileLock(path, async () => {
|
|
1768
|
+
await writeFile(path, redactSecrets(JSON.stringify(record)), "utf8");
|
|
1769
|
+
});
|
|
1770
|
+
}
|
|
1771
|
+
// SE33 (HIGH-1 fix) — atomic read-modify-write: the read that feeds `mutate`
|
|
1772
|
+
// happens INSIDE the same file lock as the write, so two concurrent progress
|
|
1773
|
+
// write-backs on one thread cannot both read a stale `runsUsed` and drop turns.
|
|
1774
|
+
async updateObjectiveRecord(conversationId, mutate) {
|
|
1775
|
+
const path = this.#objectivePath(conversationId);
|
|
1776
|
+
await mkdir(dirname(path), { recursive: true });
|
|
1777
|
+
await withFileLock(path, async () => {
|
|
1778
|
+
let current;
|
|
1779
|
+
try {
|
|
1780
|
+
current = coerceObjectiveRecord(JSON.parse(await readFile(path, "utf8")));
|
|
1781
|
+
} catch (cause) {
|
|
1782
|
+
if (cause.code !== "ENOENT") throw cause;
|
|
1783
|
+
}
|
|
1784
|
+
const next = mutate(current);
|
|
1785
|
+
if (next === void 0) return;
|
|
1786
|
+
if (next === null) {
|
|
1787
|
+
await rm(path, { force: true });
|
|
1788
|
+
return;
|
|
1789
|
+
}
|
|
1790
|
+
await writeFile(path, redactSecrets(JSON.stringify(next)), "utf8");
|
|
1791
|
+
});
|
|
1792
|
+
}
|
|
1793
|
+
async dispose() {
|
|
1794
|
+
}
|
|
1795
|
+
};
|
|
1796
|
+
}
|
|
1797
|
+
});
|
|
1798
|
+
async function withToolWhitelist(whitelist, fn) {
|
|
1799
|
+
return toolWhitelistStore.run(whitelist, fn);
|
|
1800
|
+
}
|
|
1801
|
+
function currentToolWhitelist() {
|
|
1802
|
+
return toolWhitelistStore.getStore();
|
|
1803
|
+
}
|
|
1804
|
+
function checkToolWhitelist(toolName) {
|
|
1805
|
+
const whitelist = currentToolWhitelist();
|
|
1806
|
+
if (whitelist === void 0) return { allowed: true };
|
|
1807
|
+
if (!whitelist.has(toolName)) {
|
|
1808
|
+
return {
|
|
1809
|
+
allowed: false,
|
|
1810
|
+
reason: `Tool "${toolName}" not available in this fork context`
|
|
1811
|
+
};
|
|
1812
|
+
}
|
|
1813
|
+
return { allowed: true };
|
|
1814
|
+
}
|
|
1815
|
+
var toolWhitelistStore;
|
|
1816
|
+
var init_async_local_storage = __esm({
|
|
1817
|
+
"src/internal/runtime/concurrency/async-local-storage.ts"() {
|
|
1818
|
+
toolWhitelistStore = new AsyncLocalStorage();
|
|
1819
|
+
}
|
|
1820
|
+
});
|
|
1821
|
+
|
|
1822
|
+
// src/internal/runtime/concurrency/async-semaphore.ts
|
|
1823
|
+
function createSemaphore(permits) {
|
|
1824
|
+
if (!Number.isInteger(permits) || permits < 1) {
|
|
1825
|
+
throw new ConfigurationError(
|
|
1826
|
+
`async-semaphore: permits must be a positive integer, got ${permits}`,
|
|
1827
|
+
{ code: "invalid_concurrency" }
|
|
1828
|
+
);
|
|
1829
|
+
}
|
|
1830
|
+
let active = 0;
|
|
1831
|
+
const queue = [];
|
|
1832
|
+
function tryGrant() {
|
|
1833
|
+
if (active < permits && queue.length > 0) {
|
|
1834
|
+
const resolve3 = queue.shift();
|
|
1835
|
+
if (resolve3 !== void 0) {
|
|
1836
|
+
active += 1;
|
|
1837
|
+
resolve3();
|
|
1838
|
+
}
|
|
1839
|
+
}
|
|
1840
|
+
}
|
|
1841
|
+
return {
|
|
1842
|
+
inFlight: () => active,
|
|
1843
|
+
pending: () => queue.length + active,
|
|
1844
|
+
async acquire() {
|
|
1845
|
+
await new Promise((resolve3) => {
|
|
1846
|
+
queue.push(resolve3);
|
|
1847
|
+
tryGrant();
|
|
1848
|
+
});
|
|
1849
|
+
let released = false;
|
|
1850
|
+
return () => {
|
|
1851
|
+
if (released) return;
|
|
1852
|
+
released = true;
|
|
1853
|
+
active -= 1;
|
|
1854
|
+
tryGrant();
|
|
1855
|
+
};
|
|
1856
|
+
}
|
|
1857
|
+
};
|
|
1858
|
+
}
|
|
1859
|
+
var init_async_semaphore = __esm({
|
|
1860
|
+
"src/internal/runtime/concurrency/async-semaphore.ts"() {
|
|
1861
|
+
init_errors();
|
|
1862
|
+
}
|
|
1863
|
+
});
|
|
1864
|
+
|
|
1865
|
+
// src/internal/llm/credential-pool-types.ts
|
|
1866
|
+
var COOLDOWN_MS, DEFAULT_COOLDOWN_MS;
|
|
1867
|
+
var init_credential_pool_types = __esm({
|
|
1868
|
+
"src/internal/llm/credential-pool-types.ts"() {
|
|
1869
|
+
COOLDOWN_MS = {
|
|
1870
|
+
401: 5 * 60 * 1e3,
|
|
1871
|
+
// 5 minutes — OAuth refresh can recover quickly
|
|
1872
|
+
402: 60 * 60 * 1e3,
|
|
1873
|
+
// 1 hour — billing quota typically hourly+
|
|
1874
|
+
429: 60 * 60 * 1e3
|
|
1875
|
+
// 1 hour — daily-rate-limit windows
|
|
1876
|
+
};
|
|
1877
|
+
DEFAULT_COOLDOWN_MS = 60 * 60 * 1e3;
|
|
1878
|
+
}
|
|
1879
|
+
});
|
|
1880
|
+
|
|
1881
|
+
// src/internal/llm/retry.ts
|
|
1882
|
+
function computeBackoffMs(opts) {
|
|
1883
|
+
const base = opts.baseMs ?? DEFAULT_BASE_MS;
|
|
1884
|
+
const cap2 = opts.capMs ?? DEFAULT_CAP_MS;
|
|
1885
|
+
const rng = opts.rng ?? Math.random;
|
|
1455
1886
|
if (opts.retryAfterMs !== void 0 && opts.retryAfterMs >= 0) {
|
|
1456
1887
|
return Math.max(base, Math.min(cap2, opts.retryAfterMs));
|
|
1457
1888
|
}
|
|
@@ -1737,6 +2168,151 @@ var init_credential_pool_context = __esm({
|
|
|
1737
2168
|
}
|
|
1738
2169
|
});
|
|
1739
2170
|
|
|
2171
|
+
// src/internal/runtime/objective/objective-store.ts
|
|
2172
|
+
function canPersist(a) {
|
|
2173
|
+
return typeof a.getObjectiveRecord === "function" && typeof a.setObjectiveRecord === "function";
|
|
2174
|
+
}
|
|
2175
|
+
async function mutateObjective(adapter, conversationId, mutate) {
|
|
2176
|
+
if (typeof adapter.updateObjectiveRecord === "function") {
|
|
2177
|
+
await adapter.updateObjectiveRecord(conversationId, mutate);
|
|
2178
|
+
return;
|
|
2179
|
+
}
|
|
2180
|
+
const current = await adapter.getObjectiveRecord(conversationId);
|
|
2181
|
+
const next = mutate(current);
|
|
2182
|
+
if (next === void 0) return;
|
|
2183
|
+
await adapter.setObjectiveRecord(conversationId, next);
|
|
2184
|
+
}
|
|
2185
|
+
async function getObjective(adapter, conversationId) {
|
|
2186
|
+
if (!canPersist(adapter)) return void 0;
|
|
2187
|
+
return adapter.getObjectiveRecord(conversationId);
|
|
2188
|
+
}
|
|
2189
|
+
async function setObjective(adapter, conversationId, objective, options) {
|
|
2190
|
+
if (!canPersist(adapter)) return;
|
|
2191
|
+
const record = {
|
|
2192
|
+
_schemaVersion: 1,
|
|
2193
|
+
objective,
|
|
2194
|
+
...options !== void 0 ? { options } : {},
|
|
2195
|
+
status: "active",
|
|
2196
|
+
runsUsed: 0
|
|
2197
|
+
};
|
|
2198
|
+
await adapter.setObjectiveRecord(conversationId, record);
|
|
2199
|
+
}
|
|
2200
|
+
async function updateObjectiveOptions(adapter, conversationId, patch) {
|
|
2201
|
+
if (!canPersist(adapter)) return;
|
|
2202
|
+
await mutateObjective(
|
|
2203
|
+
adapter,
|
|
2204
|
+
conversationId,
|
|
2205
|
+
(current) => current === void 0 ? void 0 : { ...current, options: { ...current.options, ...patch } }
|
|
2206
|
+
);
|
|
2207
|
+
}
|
|
2208
|
+
async function writeObjectiveProgress(adapter, conversationId, progress) {
|
|
2209
|
+
if (!canPersist(adapter)) return;
|
|
2210
|
+
await mutateObjective(
|
|
2211
|
+
adapter,
|
|
2212
|
+
conversationId,
|
|
2213
|
+
(current) => current === void 0 ? void 0 : { ...current, runsUsed: progress.runsUsed, status: progress.status }
|
|
2214
|
+
);
|
|
2215
|
+
}
|
|
2216
|
+
async function clearObjective(adapter, conversationId) {
|
|
2217
|
+
if (!canPersist(adapter)) return;
|
|
2218
|
+
await adapter.setObjectiveRecord(conversationId, null);
|
|
2219
|
+
}
|
|
2220
|
+
var init_objective_store = __esm({
|
|
2221
|
+
"src/internal/runtime/objective/objective-store.ts"() {
|
|
2222
|
+
}
|
|
2223
|
+
});
|
|
2224
|
+
|
|
2225
|
+
// src/internal/runtime/local-agent/local-agent-goal-extensions.ts
|
|
2226
|
+
var local_agent_goal_extensions_exports = {};
|
|
2227
|
+
__export(local_agent_goal_extensions_exports, {
|
|
2228
|
+
formatObjectiveProjection: () => formatObjectiveProjection,
|
|
2229
|
+
localAgentClearObjective: () => localAgentClearObjective,
|
|
2230
|
+
localAgentGetObjective: () => localAgentGetObjective,
|
|
2231
|
+
localAgentSetObjective: () => localAgentSetObjective,
|
|
2232
|
+
localAgentUpdateObjectiveOptions: () => localAgentUpdateObjectiveOptions,
|
|
2233
|
+
persistDurableProgress: () => persistDurableProgress,
|
|
2234
|
+
resolveCurrentObjectiveText: () => resolveCurrentObjectiveText,
|
|
2235
|
+
resolveDurableRun: () => resolveDurableRun
|
|
2236
|
+
});
|
|
2237
|
+
function resolveAdapter(handle) {
|
|
2238
|
+
return typeof handle === "string" ? new FileSystemConversationStorage({ root: handle }) : handle;
|
|
2239
|
+
}
|
|
2240
|
+
function assertValidGoalOptions(options) {
|
|
2241
|
+
if (options.maxRuns !== void 0 && (!Number.isInteger(options.maxRuns) || options.maxRuns <= 0)) {
|
|
2242
|
+
throw new ConfigurationError(`maxRuns must be a positive integer, got ${options.maxRuns}`, {
|
|
2243
|
+
code: "invalid_objective_max_runs"
|
|
2244
|
+
});
|
|
2245
|
+
}
|
|
2246
|
+
}
|
|
2247
|
+
async function localAgentSetObjective(handle, objective, opts) {
|
|
2248
|
+
const { threadId, ...options } = opts;
|
|
2249
|
+
assertValidGoalOptions(options);
|
|
2250
|
+
await setObjective(
|
|
2251
|
+
resolveAdapter(handle),
|
|
2252
|
+
threadId,
|
|
2253
|
+
objective,
|
|
2254
|
+
Object.keys(options).length > 0 ? options : void 0
|
|
2255
|
+
);
|
|
2256
|
+
}
|
|
2257
|
+
async function localAgentGetObjective(handle, opts) {
|
|
2258
|
+
return getObjective(resolveAdapter(handle), opts.threadId);
|
|
2259
|
+
}
|
|
2260
|
+
async function localAgentUpdateObjectiveOptions(handle, opts) {
|
|
2261
|
+
const { threadId, ...patch } = opts;
|
|
2262
|
+
assertValidGoalOptions(patch);
|
|
2263
|
+
await updateObjectiveOptions(resolveAdapter(handle), threadId, patch);
|
|
2264
|
+
}
|
|
2265
|
+
async function localAgentClearObjective(handle, opts) {
|
|
2266
|
+
await clearObjective(resolveAdapter(handle), opts.threadId);
|
|
2267
|
+
}
|
|
2268
|
+
async function resolveCurrentObjectiveText(handle, threadId) {
|
|
2269
|
+
const record = await getObjective(resolveAdapter(handle), threadId);
|
|
2270
|
+
return record?.status === "active" ? record.objective : void 0;
|
|
2271
|
+
}
|
|
2272
|
+
function formatObjectiveProjection(objective, assembled) {
|
|
2273
|
+
const signal = `<current-objective>
|
|
2274
|
+
${objective}
|
|
2275
|
+
</current-objective>`;
|
|
2276
|
+
return assembled === void 0 || assembled.length === 0 ? signal : `${signal}
|
|
2277
|
+
|
|
2278
|
+
${assembled}`;
|
|
2279
|
+
}
|
|
2280
|
+
async function resolveDurableRun(handle, goalConfig, threadId, callerOptions) {
|
|
2281
|
+
const record = await getObjective(resolveAdapter(handle), threadId);
|
|
2282
|
+
if (record === void 0) return { kind: "none" };
|
|
2283
|
+
const judgeModel = callerOptions?.judgeModel ?? record.options?.judgeModel ?? goalConfig?.judgeModel;
|
|
2284
|
+
if (judgeModel === void 0) return { kind: "inert" };
|
|
2285
|
+
const totalBudget = record.options?.maxRuns ?? goalConfig?.maxRuns ?? DEFAULT_MAX_RUNS;
|
|
2286
|
+
const remaining = Math.max(0, totalBudget - record.runsUsed);
|
|
2287
|
+
if (remaining === 0) return { kind: "exhausted" };
|
|
2288
|
+
const perCall = callerOptions?.maxTurns;
|
|
2289
|
+
const maxTurns = perCall !== void 0 ? Math.min(perCall, remaining) : remaining;
|
|
2290
|
+
return {
|
|
2291
|
+
kind: "run",
|
|
2292
|
+
goal: record.objective,
|
|
2293
|
+
options: { ...callerOptions, maxTurns, judgeModel }
|
|
2294
|
+
};
|
|
2295
|
+
}
|
|
2296
|
+
async function persistDurableProgress(handle, threadId, result) {
|
|
2297
|
+
const adapter = resolveAdapter(handle);
|
|
2298
|
+
const record = await getObjective(adapter, threadId);
|
|
2299
|
+
if (record === void 0) return;
|
|
2300
|
+
const status = result.status === "completed" ? "done" : result.status === "paused" ? "paused" : "active";
|
|
2301
|
+
await writeObjectiveProgress(adapter, threadId, {
|
|
2302
|
+
runsUsed: record.runsUsed + result.turnsUsed,
|
|
2303
|
+
status
|
|
2304
|
+
});
|
|
2305
|
+
}
|
|
2306
|
+
var DEFAULT_MAX_RUNS;
|
|
2307
|
+
var init_local_agent_goal_extensions = __esm({
|
|
2308
|
+
"src/internal/runtime/local-agent/local-agent-goal-extensions.ts"() {
|
|
2309
|
+
init_errors();
|
|
2310
|
+
init_conversation_storage_fs();
|
|
2311
|
+
init_objective_store();
|
|
2312
|
+
DEFAULT_MAX_RUNS = 20;
|
|
2313
|
+
}
|
|
2314
|
+
});
|
|
2315
|
+
|
|
1740
2316
|
// src/internal/memory/embedding-cache.ts
|
|
1741
2317
|
var LruEmbeddingCache, globalEmbeddingCache;
|
|
1742
2318
|
var init_embedding_cache = __esm({
|
|
@@ -7920,68 +8496,7 @@ init_cwd_mutex();
|
|
|
7920
8496
|
|
|
7921
8497
|
// src/internal/personality/store.ts
|
|
7922
8498
|
init_atomic_write();
|
|
7923
|
-
|
|
7924
|
-
// src/internal/persistence/file-lock.ts
|
|
7925
|
-
init_cwd_mutex();
|
|
7926
|
-
var cached;
|
|
7927
|
-
var warnedMissing = false;
|
|
7928
|
-
var warnedStructural = false;
|
|
7929
|
-
async function getProperLockfile() {
|
|
7930
|
-
if (cached !== void 0) return cached;
|
|
7931
|
-
try {
|
|
7932
|
-
const mod = await import('proper-lockfile');
|
|
7933
|
-
if (!validateLockModule(mod)) {
|
|
7934
|
-
if (!warnedStructural) {
|
|
7935
|
-
warnedStructural = true;
|
|
7936
|
-
process.stderr.write(
|
|
7937
|
-
"[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"
|
|
7938
|
-
);
|
|
7939
|
-
}
|
|
7940
|
-
cached = null;
|
|
7941
|
-
return cached;
|
|
7942
|
-
}
|
|
7943
|
-
cached = mod;
|
|
7944
|
-
} catch {
|
|
7945
|
-
cached = null;
|
|
7946
|
-
}
|
|
7947
|
-
return cached;
|
|
7948
|
-
}
|
|
7949
|
-
function validateLockModule(mod) {
|
|
7950
|
-
if (mod === null || mod === void 0 || typeof mod !== "object") return false;
|
|
7951
|
-
const m = mod;
|
|
7952
|
-
return typeof m.lock === "function" && typeof m.unlock === "function";
|
|
7953
|
-
}
|
|
7954
|
-
async function withFileLock(path, fn, options) {
|
|
7955
|
-
const lib = await getProperLockfile();
|
|
7956
|
-
if (lib === null) {
|
|
7957
|
-
if (!warnedMissing) {
|
|
7958
|
-
warnedMissing = true;
|
|
7959
|
-
process.stderr.write(
|
|
7960
|
-
"[theokit-sdk] proper-lockfile not installed; cross-process file lock unavailable. Install with: pnpm add proper-lockfile\n"
|
|
7961
|
-
);
|
|
7962
|
-
}
|
|
7963
|
-
return withCwdMutex(`file-lock:${path}`, fn);
|
|
7964
|
-
}
|
|
7965
|
-
return withCwdMutex(`file-lock:${path}`, async () => {
|
|
7966
|
-
const release = await lib.lock(path, {
|
|
7967
|
-
// EC-1: companion lockfile, target path may not exist yet.
|
|
7968
|
-
lockfilePath: `${path}.lock`,
|
|
7969
|
-
realpath: false,
|
|
7970
|
-
stale: 3e4,
|
|
7971
|
-
retries: {
|
|
7972
|
-
retries: 5,
|
|
7973
|
-
factor: 1.5,
|
|
7974
|
-
minTimeout: 100,
|
|
7975
|
-
maxTimeout: 5e3
|
|
7976
|
-
}
|
|
7977
|
-
});
|
|
7978
|
-
try {
|
|
7979
|
-
return await fn();
|
|
7980
|
-
} finally {
|
|
7981
|
-
await release();
|
|
7982
|
-
}
|
|
7983
|
-
});
|
|
7984
|
-
}
|
|
8499
|
+
init_file_lock();
|
|
7985
8500
|
var THEOKIT_DIR_NAME = ".theokit";
|
|
7986
8501
|
function getTheokitHome(cwd) {
|
|
7987
8502
|
const override = process.env.THEOKIT_HOME?.trim();
|
|
@@ -8856,431 +9371,174 @@ function resolveChildEnv(options = {}) {
|
|
|
8856
9371
|
if (value !== void 0 && inheritsUnderPolicy(name, policy)) base[name] = value;
|
|
8857
9372
|
}
|
|
8858
9373
|
for (const [name, value] of Object.entries(options.overrides ?? {})) {
|
|
8859
|
-
base[name] = value;
|
|
8860
|
-
}
|
|
8861
|
-
return base;
|
|
8862
|
-
}
|
|
8863
|
-
|
|
8864
|
-
// src/internal/runtime/lifecycle/spawn-collect.ts
|
|
8865
|
-
function spawnAndCollect(options) {
|
|
8866
|
-
return new Promise((resolve3) => {
|
|
8867
|
-
const timeoutMs = options.timeoutMs ?? 3e4;
|
|
8868
|
-
const spawnOptions = {
|
|
8869
|
-
cwd: options.cwd,
|
|
8870
|
-
// #54 — scrub secret-like parent env by default; `options.env` still wins.
|
|
8871
|
-
env: resolveChildEnv({ policy: options.envPolicy, overrides: options.env })
|
|
8872
|
-
};
|
|
8873
|
-
const child = spawn(options.command, options.args ?? [], spawnOptions);
|
|
8874
|
-
let stdout = "";
|
|
8875
|
-
let stderr = "";
|
|
8876
|
-
let timedOut = false;
|
|
8877
|
-
let settled = false;
|
|
8878
|
-
const settle = (result) => {
|
|
8879
|
-
if (settled) return;
|
|
8880
|
-
settled = true;
|
|
8881
|
-
resolve3(result);
|
|
8882
|
-
};
|
|
8883
|
-
const timer = setTimeout(() => {
|
|
8884
|
-
timedOut = true;
|
|
8885
|
-
try {
|
|
8886
|
-
child.kill("SIGKILL");
|
|
8887
|
-
} catch {
|
|
8888
|
-
}
|
|
8889
|
-
settle({ stdout, stderr, exitCode: null, timedOut: true });
|
|
8890
|
-
}, timeoutMs);
|
|
8891
|
-
child.stdout?.on("data", (chunk) => {
|
|
8892
|
-
stdout += chunk.toString("utf8");
|
|
8893
|
-
});
|
|
8894
|
-
child.stderr?.on("data", (chunk) => {
|
|
8895
|
-
stderr += chunk.toString("utf8");
|
|
8896
|
-
});
|
|
8897
|
-
child.on("error", (cause) => {
|
|
8898
|
-
clearTimeout(timer);
|
|
8899
|
-
settle({ stdout, stderr, exitCode: -1, timedOut, spawnError: cause });
|
|
8900
|
-
});
|
|
8901
|
-
child.on("close", (code) => {
|
|
8902
|
-
clearTimeout(timer);
|
|
8903
|
-
settle({ stdout, stderr, exitCode: code, timedOut });
|
|
8904
|
-
});
|
|
8905
|
-
if (options.stdin !== void 0 && child.stdin !== null) {
|
|
8906
|
-
child.stdin.end(options.stdin);
|
|
8907
|
-
}
|
|
8908
|
-
});
|
|
8909
|
-
}
|
|
8910
|
-
|
|
8911
|
-
// src/internal/runtime/hooks/hooks-executor.ts
|
|
8912
|
-
init_hooks_source();
|
|
8913
|
-
var HooksExecutor = class {
|
|
8914
|
-
constructor(cwd) {
|
|
8915
|
-
this.cwd = cwd;
|
|
8916
|
-
}
|
|
8917
|
-
cwd;
|
|
8918
|
-
config = {};
|
|
8919
|
-
async initialize(settingSourcesIncludeProject) {
|
|
8920
|
-
if (!settingSourcesIncludeProject) {
|
|
8921
|
-
this.config = {};
|
|
8922
|
-
return;
|
|
8923
|
-
}
|
|
8924
|
-
this.config = await loadHookConfig(this.cwd);
|
|
8925
|
-
}
|
|
8926
|
-
/** Fire every hook registered for `event` and aggregate the decisions. */
|
|
8927
|
-
async run(payload) {
|
|
8928
|
-
const commands = this.commandsFor(payload.event, payload.tool);
|
|
8929
|
-
if (commands.length === 0) return { decisions: [], blocked: false };
|
|
8930
|
-
const decisions = [];
|
|
8931
|
-
for (const command of commands) {
|
|
8932
|
-
const decision = await this.executeOne(command, payload);
|
|
8933
|
-
decisions.push(decision);
|
|
8934
|
-
if (decision.decision === "deny") {
|
|
8935
|
-
const result = {
|
|
8936
|
-
decisions,
|
|
8937
|
-
blocked: true
|
|
8938
|
-
};
|
|
8939
|
-
if (decision.reason !== void 0) result.reason = decision.reason;
|
|
8940
|
-
return result;
|
|
8941
|
-
}
|
|
8942
|
-
}
|
|
8943
|
-
return { decisions, blocked: false };
|
|
8944
|
-
}
|
|
8945
|
-
commandsFor(event, tool) {
|
|
8946
|
-
const list2 = this.config.hooks?.[event] ?? [];
|
|
8947
|
-
if (tool === void 0) return list2;
|
|
8948
|
-
return list2.filter((entry) => {
|
|
8949
|
-
if (entry.matcher === void 0) return true;
|
|
8950
|
-
try {
|
|
8951
|
-
return new RegExp(entry.matcher).test(tool);
|
|
8952
|
-
} catch {
|
|
8953
|
-
return entry.matcher === tool;
|
|
8954
|
-
}
|
|
8955
|
-
});
|
|
8956
|
-
}
|
|
8957
|
-
async executeOne(command, payload) {
|
|
8958
|
-
const timeoutMs = command.timeoutMs ?? 3e4;
|
|
8959
|
-
const result = await spawnAndCollect({
|
|
8960
|
-
command: "sh",
|
|
8961
|
-
args: ["-c", command.command],
|
|
8962
|
-
cwd: this.cwd,
|
|
8963
|
-
timeoutMs,
|
|
8964
|
-
stdin: JSON.stringify(payload)
|
|
8965
|
-
});
|
|
8966
|
-
if (result.timedOut) {
|
|
8967
|
-
return { decision: "deny", reason: `Hook timed out after ${timeoutMs}ms` };
|
|
8968
|
-
}
|
|
8969
|
-
if (result.spawnError !== void 0) {
|
|
8970
|
-
return { decision: "deny", reason: `Hook spawn failed: ${result.spawnError.message}` };
|
|
8971
|
-
}
|
|
8972
|
-
if (result.exitCode !== 0) {
|
|
8973
|
-
return {
|
|
8974
|
-
decision: "deny",
|
|
8975
|
-
reason: result.stderr.trim().length > 0 ? result.stderr.trim() : `Hook exited with code ${result.exitCode}`
|
|
8976
|
-
};
|
|
8977
|
-
}
|
|
8978
|
-
return parseDecisionFromStdout(result.stdout);
|
|
8979
|
-
}
|
|
8980
|
-
};
|
|
8981
|
-
function parseDecisionFromStdout(stdout) {
|
|
8982
|
-
const trimmed = stdout.trim();
|
|
8983
|
-
if (trimmed.length === 0) return { decision: "allow" };
|
|
8984
|
-
try {
|
|
8985
|
-
const parsed = JSON.parse(trimmed);
|
|
8986
|
-
if (parsed.decision === "deny" || parsed.decision === "feedback") {
|
|
8987
|
-
const result = { decision: parsed.decision };
|
|
8988
|
-
if (parsed.reason !== void 0) result.reason = parsed.reason;
|
|
8989
|
-
if (parsed.feedback !== void 0) result.feedback = parsed.feedback;
|
|
8990
|
-
return result;
|
|
8991
|
-
}
|
|
8992
|
-
if (parsed.decision === "allow") return { decision: "allow" };
|
|
8993
|
-
} catch {
|
|
8994
|
-
return { decision: "feedback", feedback: trimmed };
|
|
8995
|
-
}
|
|
8996
|
-
return { decision: "allow" };
|
|
8997
|
-
}
|
|
8998
|
-
|
|
8999
|
-
// src/internal/runtime/lifecycle/post-run-lifecycle.ts
|
|
9000
|
-
init_session_summary_writer();
|
|
9001
|
-
|
|
9002
|
-
// src/internal/runtime/memory/memory-path-selector.ts
|
|
9003
|
-
var PORT_MEMORY_PATH_ENV_VAR = "THEOKIT_PORT_MEMORY_PATH";
|
|
9004
|
-
function shouldUsePortMemoryPath() {
|
|
9005
|
-
const env = globalThis.process?.env;
|
|
9006
|
-
if (env === void 0) return false;
|
|
9007
|
-
const val = env[PORT_MEMORY_PATH_ENV_VAR];
|
|
9008
|
-
return val === "1" || val === "true";
|
|
9009
|
-
}
|
|
9010
|
-
function resolveMemoryProviderForLoop(consumerSupplied, defaultAdapter, portPathEnabled) {
|
|
9011
|
-
if (consumerSupplied !== void 0) return consumerSupplied;
|
|
9012
|
-
if (portPathEnabled) return defaultAdapter;
|
|
9013
|
-
return void 0;
|
|
9014
|
-
}
|
|
9015
|
-
function resolveMemoryToolsForLoop(legacyTools, portPathEnabled) {
|
|
9016
|
-
if (portPathEnabled) return void 0;
|
|
9017
|
-
return legacyTools;
|
|
9018
|
-
}
|
|
9019
|
-
function resolveActiveMemorySummaryForSend(legacySummary, portPathEnabled) {
|
|
9020
|
-
if (portPathEnabled) return void 0;
|
|
9021
|
-
return legacySummary;
|
|
9022
|
-
}
|
|
9023
|
-
|
|
9024
|
-
// src/internal/runtime/session/agent-session-store.ts
|
|
9025
|
-
init_atomic_write();
|
|
9026
|
-
init_security();
|
|
9027
|
-
var VALID_ROLES = /* @__PURE__ */ new Set([
|
|
9028
|
-
"user",
|
|
9029
|
-
"assistant",
|
|
9030
|
-
"system",
|
|
9031
|
-
"tool_call",
|
|
9032
|
-
"tool_result"
|
|
9033
|
-
]);
|
|
9034
|
-
function sessionFilePath(cwd, agentId) {
|
|
9035
|
-
const safe2 = sanitizeIdentifier(agentId, { maxLen: 128 });
|
|
9036
|
-
return safePathJoin(cwd, ".theokit", "agents", safe2, "messages.jsonl");
|
|
9037
|
-
}
|
|
9038
|
-
async function readJsonlLines(cwd, agentId) {
|
|
9039
|
-
const path = sessionFilePath(cwd, agentId);
|
|
9040
|
-
try {
|
|
9041
|
-
const raw = await readFile(path, "utf8");
|
|
9042
|
-
return raw.split("\n").filter((line) => line.length > 0);
|
|
9043
|
-
} catch {
|
|
9044
|
-
return [];
|
|
9045
|
-
}
|
|
9046
|
-
}
|
|
9047
|
-
function warnMalformed(agentId, line) {
|
|
9048
|
-
process.stderr.write(
|
|
9049
|
-
`[theokit-sdk] skipping malformed line in messages.jsonl (${agentId}): ${line.slice(0, 80)}...
|
|
9050
|
-
`
|
|
9051
|
-
);
|
|
9052
|
-
}
|
|
9053
|
-
function hydrateSessionLine(parsed) {
|
|
9054
|
-
if (typeof parsed.text !== "string" || parsed.role === void 0) return void 0;
|
|
9055
|
-
if (parsed.role === "user" || parsed.role === "assistant") {
|
|
9056
|
-
return { role: parsed.role, text: parsed.text };
|
|
9057
|
-
}
|
|
9058
|
-
if (parsed.role === "tool_call" || parsed.role === "tool_result") {
|
|
9059
|
-
const label = parsed.role === "tool_call" ? "tool call" : "tool result";
|
|
9060
|
-
return { role: "assistant", text: `[${label}] ${parsed.text}` };
|
|
9061
|
-
}
|
|
9062
|
-
return void 0;
|
|
9063
|
-
}
|
|
9064
|
-
async function readSessionFile(cwd, agentId) {
|
|
9065
|
-
const lines = await readJsonlLines(cwd, agentId);
|
|
9066
|
-
const messages = [];
|
|
9067
|
-
for (const line of lines) {
|
|
9068
|
-
try {
|
|
9069
|
-
const msg = hydrateSessionLine(JSON.parse(line));
|
|
9070
|
-
if (msg !== void 0) messages.push(msg);
|
|
9071
|
-
} catch {
|
|
9072
|
-
warnMalformed(agentId, line);
|
|
9073
|
-
}
|
|
9074
|
-
}
|
|
9075
|
-
return messages;
|
|
9076
|
-
}
|
|
9077
|
-
async function readAllPersistedMessages(cwd, agentId) {
|
|
9078
|
-
const lines = await readJsonlLines(cwd, agentId);
|
|
9079
|
-
const messages = [];
|
|
9080
|
-
for (const line of lines) {
|
|
9081
|
-
try {
|
|
9082
|
-
const parsed = JSON.parse(line);
|
|
9083
|
-
if (parsed.role !== void 0 && VALID_ROLES.has(parsed.role) && typeof parsed.text === "string") {
|
|
9084
|
-
messages.push({
|
|
9085
|
-
role: parsed.role,
|
|
9086
|
-
text: parsed.text,
|
|
9087
|
-
at: typeof parsed.at === "number" ? parsed.at : Date.now()
|
|
9088
|
-
});
|
|
9089
|
-
}
|
|
9090
|
-
} catch {
|
|
9091
|
-
warnMalformed(agentId, line);
|
|
9092
|
-
}
|
|
9093
|
-
}
|
|
9094
|
-
return messages;
|
|
9095
|
-
}
|
|
9096
|
-
async function appendAnyPersistedMessage(cwd, agentId, record) {
|
|
9097
|
-
await appendPersistedMessages(cwd, agentId, [record]);
|
|
9098
|
-
}
|
|
9099
|
-
async function appendPersistedMessages(cwd, agentId, records) {
|
|
9100
|
-
if (records.length === 0) return;
|
|
9101
|
-
const path = sessionFilePath(cwd, agentId);
|
|
9102
|
-
const payload = records.map((r) => `${redactSecrets(JSON.stringify(r))}
|
|
9103
|
-
`).join("");
|
|
9104
|
-
const dir = dirname(path);
|
|
9105
|
-
let written = false;
|
|
9106
|
-
const attempt = async () => {
|
|
9107
|
-
await mkdir(dir, { recursive: true });
|
|
9108
|
-
await withFileLock(path, async () => {
|
|
9109
|
-
await appendFile(path, payload, "utf8");
|
|
9110
|
-
written = true;
|
|
9111
|
-
});
|
|
9112
|
-
};
|
|
9113
|
-
try {
|
|
9114
|
-
await attempt();
|
|
9115
|
-
} catch (cause) {
|
|
9116
|
-
if (written || cause.code !== "ENOENT") throw cause;
|
|
9117
|
-
await attempt();
|
|
9118
|
-
}
|
|
9119
|
-
}
|
|
9120
|
-
async function rewriteLockedSession(path, transform) {
|
|
9121
|
-
await withFileLock(path, async () => {
|
|
9122
|
-
let raw;
|
|
9123
|
-
try {
|
|
9124
|
-
raw = await readFile(path, "utf8");
|
|
9125
|
-
} catch {
|
|
9126
|
-
return;
|
|
9127
|
-
}
|
|
9128
|
-
const lines = raw.split("\n").filter((line) => line.length > 0);
|
|
9129
|
-
const next = transform(lines);
|
|
9130
|
-
if (next === void 0) return;
|
|
9131
|
-
await replaceFileAtomic(path, next);
|
|
9132
|
-
});
|
|
9133
|
-
}
|
|
9134
|
-
async function compactSessionFile(cwd, agentId, maxTurns) {
|
|
9135
|
-
const path = sessionFilePath(cwd, agentId);
|
|
9136
|
-
if (!existsSync(path)) return;
|
|
9137
|
-
await rewriteLockedSession(
|
|
9138
|
-
path,
|
|
9139
|
-
(lines) => lines.length <= maxTurns * 2 ? void 0 : `${lines.slice(-maxTurns).join("\n")}
|
|
9140
|
-
`
|
|
9141
|
-
);
|
|
9142
|
-
}
|
|
9143
|
-
async function truncateSessionTo(cwd, agentId, keepCount) {
|
|
9144
|
-
const path = sessionFilePath(cwd, agentId);
|
|
9145
|
-
if (!existsSync(path)) return 0;
|
|
9146
|
-
let kept = 0;
|
|
9147
|
-
await rewriteLockedSession(path, (lines) => {
|
|
9148
|
-
const keep = Math.max(0, Math.min(keepCount, lines.length));
|
|
9149
|
-
kept = keep;
|
|
9150
|
-
if (keep === lines.length) return void 0;
|
|
9151
|
-
return keep === 0 ? "" : `${lines.slice(0, keep).join("\n")}
|
|
9152
|
-
`;
|
|
9153
|
-
});
|
|
9154
|
-
return kept;
|
|
9155
|
-
}
|
|
9156
|
-
|
|
9157
|
-
// src/internal/persistence/conversation-storage-fs.ts
|
|
9158
|
-
init_security();
|
|
9159
|
-
|
|
9160
|
-
// src/internal/persistence/pagination.ts
|
|
9161
|
-
function paginate(items, opts) {
|
|
9162
|
-
if (opts === void 0 || opts.offset === void 0 && opts.limit === void 0) return items;
|
|
9163
|
-
const start = Math.max(0, opts.offset ?? 0);
|
|
9164
|
-
const end = opts.limit === void 0 ? items.length : start + Math.max(0, opts.limit);
|
|
9165
|
-
return items.slice(start, end);
|
|
9374
|
+
base[name] = value;
|
|
9375
|
+
}
|
|
9376
|
+
return base;
|
|
9166
9377
|
}
|
|
9167
9378
|
|
|
9168
|
-
// src/internal/
|
|
9169
|
-
function
|
|
9170
|
-
|
|
9171
|
-
|
|
9172
|
-
|
|
9173
|
-
|
|
9174
|
-
|
|
9175
|
-
|
|
9176
|
-
|
|
9177
|
-
|
|
9178
|
-
|
|
9179
|
-
|
|
9180
|
-
|
|
9181
|
-
|
|
9182
|
-
|
|
9183
|
-
|
|
9184
|
-
|
|
9185
|
-
|
|
9379
|
+
// src/internal/runtime/lifecycle/spawn-collect.ts
|
|
9380
|
+
function spawnAndCollect(options) {
|
|
9381
|
+
return new Promise((resolve3) => {
|
|
9382
|
+
const timeoutMs = options.timeoutMs ?? 3e4;
|
|
9383
|
+
const spawnOptions = {
|
|
9384
|
+
cwd: options.cwd,
|
|
9385
|
+
// #54 — scrub secret-like parent env by default; `options.env` still wins.
|
|
9386
|
+
env: resolveChildEnv({ policy: options.envPolicy, overrides: options.env })
|
|
9387
|
+
};
|
|
9388
|
+
const child = spawn(options.command, options.args ?? [], spawnOptions);
|
|
9389
|
+
let stdout = "";
|
|
9390
|
+
let stderr = "";
|
|
9391
|
+
let timedOut = false;
|
|
9392
|
+
let settled = false;
|
|
9393
|
+
const settle = (result) => {
|
|
9394
|
+
if (settled) return;
|
|
9395
|
+
settled = true;
|
|
9396
|
+
resolve3(result);
|
|
9397
|
+
};
|
|
9398
|
+
const timer = setTimeout(() => {
|
|
9399
|
+
timedOut = true;
|
|
9400
|
+
try {
|
|
9401
|
+
child.kill("SIGKILL");
|
|
9402
|
+
} catch {
|
|
9403
|
+
}
|
|
9404
|
+
settle({ stdout, stderr, exitCode: null, timedOut: true });
|
|
9405
|
+
}, timeoutMs);
|
|
9406
|
+
child.stdout?.on("data", (chunk) => {
|
|
9407
|
+
stdout += chunk.toString("utf8");
|
|
9408
|
+
});
|
|
9409
|
+
child.stderr?.on("data", (chunk) => {
|
|
9410
|
+
stderr += chunk.toString("utf8");
|
|
9411
|
+
});
|
|
9412
|
+
child.on("error", (cause) => {
|
|
9413
|
+
clearTimeout(timer);
|
|
9414
|
+
settle({ stdout, stderr, exitCode: -1, timedOut, spawnError: cause });
|
|
9415
|
+
});
|
|
9416
|
+
child.on("close", (code) => {
|
|
9417
|
+
clearTimeout(timer);
|
|
9418
|
+
settle({ stdout, stderr, exitCode: code, timedOut });
|
|
9419
|
+
});
|
|
9420
|
+
if (options.stdin !== void 0 && child.stdin !== null) {
|
|
9421
|
+
child.stdin.end(options.stdin);
|
|
9422
|
+
}
|
|
9423
|
+
});
|
|
9186
9424
|
}
|
|
9187
9425
|
|
|
9188
|
-
// src/internal/
|
|
9189
|
-
|
|
9190
|
-
|
|
9191
|
-
constructor(
|
|
9192
|
-
this
|
|
9193
|
-
}
|
|
9194
|
-
/** Exposed for tests + diagnostics. The path is sanitized at use sites. */
|
|
9195
|
-
get root() {
|
|
9196
|
-
return this.#root;
|
|
9197
|
-
}
|
|
9198
|
-
async getMessages(conversationId, opts) {
|
|
9199
|
-
const records = await readAllPersistedMessages(this.#root, conversationId);
|
|
9200
|
-
const all = records.map(toStoredMessage);
|
|
9201
|
-
return paginate(all, opts);
|
|
9202
|
-
}
|
|
9203
|
-
async appendMessage(conversationId, message) {
|
|
9204
|
-
await appendAnyPersistedMessage(this.#root, conversationId, toRecord(message));
|
|
9205
|
-
}
|
|
9206
|
-
async appendMessages(conversationId, messages) {
|
|
9207
|
-
await appendPersistedMessages(this.#root, conversationId, messages.map(toRecord));
|
|
9208
|
-
}
|
|
9209
|
-
async truncateConversation(conversationId, keepCount) {
|
|
9210
|
-
return truncateSessionTo(this.#root, conversationId, keepCount);
|
|
9211
|
-
}
|
|
9212
|
-
async deleteConversation(conversationId) {
|
|
9213
|
-
const safe2 = sanitizeIdentifier(conversationId, { maxLen: 128 });
|
|
9214
|
-
const dirPath = safePathJoin(this.#root, ".theokit", "agents", safe2);
|
|
9215
|
-
await rm(dirPath, { recursive: true, force: true });
|
|
9426
|
+
// src/internal/runtime/hooks/hooks-executor.ts
|
|
9427
|
+
init_hooks_source();
|
|
9428
|
+
var HooksExecutor = class {
|
|
9429
|
+
constructor(cwd) {
|
|
9430
|
+
this.cwd = cwd;
|
|
9216
9431
|
}
|
|
9217
|
-
|
|
9218
|
-
|
|
9219
|
-
|
|
9220
|
-
|
|
9221
|
-
|
|
9222
|
-
|
|
9223
|
-
async listConversationIds(opts = {}) {
|
|
9224
|
-
const agentsRoot = safePathJoin(this.#root, ".theokit", "agents");
|
|
9225
|
-
let entries;
|
|
9226
|
-
try {
|
|
9227
|
-
entries = await readdir(agentsRoot);
|
|
9228
|
-
} catch (cause) {
|
|
9229
|
-
if (cause.code === "ENOENT") return [];
|
|
9230
|
-
throw cause;
|
|
9432
|
+
cwd;
|
|
9433
|
+
config = {};
|
|
9434
|
+
async initialize(settingSourcesIncludeProject) {
|
|
9435
|
+
if (!settingSourcesIncludeProject) {
|
|
9436
|
+
this.config = {};
|
|
9437
|
+
return;
|
|
9231
9438
|
}
|
|
9232
|
-
|
|
9233
|
-
return entries;
|
|
9234
|
-
}
|
|
9235
|
-
async compact(conversationId, maxTurns) {
|
|
9236
|
-
await compactSessionFile(this.#root, conversationId, maxTurns);
|
|
9237
|
-
}
|
|
9238
|
-
// SE4 — session metadata persisted as a per-conversation sidecar
|
|
9239
|
-
// `<root>/.theokit/agents/<safeId>/session.json` (same sanitized perimeter as
|
|
9240
|
-
// the transcript). Kept separate from messages.jsonl so a title/tag write does
|
|
9241
|
-
// not rewrite the append-only log.
|
|
9242
|
-
#metaPath(conversationId) {
|
|
9243
|
-
const safe2 = sanitizeIdentifier(conversationId, { maxLen: 128 });
|
|
9244
|
-
return safePathJoin(this.#root, ".theokit", "agents", safe2, "session.json");
|
|
9439
|
+
this.config = await loadHookConfig(this.cwd);
|
|
9245
9440
|
}
|
|
9246
|
-
|
|
9247
|
-
|
|
9248
|
-
|
|
9249
|
-
|
|
9250
|
-
|
|
9251
|
-
|
|
9252
|
-
|
|
9441
|
+
/** Fire every hook registered for `event` and aggregate the decisions. */
|
|
9442
|
+
async run(payload) {
|
|
9443
|
+
const commands = this.commandsFor(payload.event, payload.tool);
|
|
9444
|
+
if (commands.length === 0) return { decisions: [], blocked: false };
|
|
9445
|
+
const decisions = [];
|
|
9446
|
+
for (const command of commands) {
|
|
9447
|
+
const decision = await this.executeOne(command, payload);
|
|
9448
|
+
decisions.push(decision);
|
|
9449
|
+
if (decision.decision === "deny") {
|
|
9450
|
+
const result = {
|
|
9451
|
+
decisions,
|
|
9452
|
+
blocked: true
|
|
9453
|
+
};
|
|
9454
|
+
if (decision.reason !== void 0) result.reason = decision.reason;
|
|
9455
|
+
return result;
|
|
9456
|
+
}
|
|
9253
9457
|
}
|
|
9458
|
+
return { decisions, blocked: false };
|
|
9254
9459
|
}
|
|
9255
|
-
|
|
9256
|
-
const
|
|
9257
|
-
|
|
9258
|
-
|
|
9259
|
-
|
|
9460
|
+
commandsFor(event, tool) {
|
|
9461
|
+
const list2 = this.config.hooks?.[event] ?? [];
|
|
9462
|
+
if (tool === void 0) return list2;
|
|
9463
|
+
return list2.filter((entry) => {
|
|
9464
|
+
if (entry.matcher === void 0) return true;
|
|
9260
9465
|
try {
|
|
9261
|
-
|
|
9262
|
-
} catch
|
|
9263
|
-
|
|
9466
|
+
return new RegExp(entry.matcher).test(tool);
|
|
9467
|
+
} catch {
|
|
9468
|
+
return entry.matcher === tool;
|
|
9264
9469
|
}
|
|
9265
|
-
const next = applyMetaPatch(current, patch);
|
|
9266
|
-
await writeFile(metaPath, redactSecrets(JSON.stringify(next)), "utf8");
|
|
9267
9470
|
});
|
|
9268
9471
|
}
|
|
9269
|
-
async
|
|
9472
|
+
async executeOne(command, payload) {
|
|
9473
|
+
const timeoutMs = command.timeoutMs ?? 3e4;
|
|
9474
|
+
const result = await spawnAndCollect({
|
|
9475
|
+
command: "sh",
|
|
9476
|
+
args: ["-c", command.command],
|
|
9477
|
+
cwd: this.cwd,
|
|
9478
|
+
timeoutMs,
|
|
9479
|
+
stdin: JSON.stringify(payload)
|
|
9480
|
+
});
|
|
9481
|
+
if (result.timedOut) {
|
|
9482
|
+
return { decision: "deny", reason: `Hook timed out after ${timeoutMs}ms` };
|
|
9483
|
+
}
|
|
9484
|
+
if (result.spawnError !== void 0) {
|
|
9485
|
+
return { decision: "deny", reason: `Hook spawn failed: ${result.spawnError.message}` };
|
|
9486
|
+
}
|
|
9487
|
+
if (result.exitCode !== 0) {
|
|
9488
|
+
return {
|
|
9489
|
+
decision: "deny",
|
|
9490
|
+
reason: result.stderr.trim().length > 0 ? result.stderr.trim() : `Hook exited with code ${result.exitCode}`
|
|
9491
|
+
};
|
|
9492
|
+
}
|
|
9493
|
+
return parseDecisionFromStdout(result.stdout);
|
|
9270
9494
|
}
|
|
9271
9495
|
};
|
|
9272
|
-
function
|
|
9273
|
-
|
|
9274
|
-
|
|
9275
|
-
|
|
9276
|
-
|
|
9277
|
-
|
|
9496
|
+
function parseDecisionFromStdout(stdout) {
|
|
9497
|
+
const trimmed = stdout.trim();
|
|
9498
|
+
if (trimmed.length === 0) return { decision: "allow" };
|
|
9499
|
+
try {
|
|
9500
|
+
const parsed = JSON.parse(trimmed);
|
|
9501
|
+
if (parsed.decision === "deny" || parsed.decision === "feedback") {
|
|
9502
|
+
const result = { decision: parsed.decision };
|
|
9503
|
+
if (parsed.reason !== void 0) result.reason = parsed.reason;
|
|
9504
|
+
if (parsed.feedback !== void 0) result.feedback = parsed.feedback;
|
|
9505
|
+
return result;
|
|
9506
|
+
}
|
|
9507
|
+
if (parsed.decision === "allow") return { decision: "allow" };
|
|
9508
|
+
} catch {
|
|
9509
|
+
return { decision: "feedback", feedback: trimmed };
|
|
9510
|
+
}
|
|
9511
|
+
return { decision: "allow" };
|
|
9278
9512
|
}
|
|
9279
|
-
|
|
9280
|
-
|
|
9513
|
+
|
|
9514
|
+
// src/internal/runtime/lifecycle/post-run-lifecycle.ts
|
|
9515
|
+
init_session_summary_writer();
|
|
9516
|
+
|
|
9517
|
+
// src/internal/runtime/memory/memory-path-selector.ts
|
|
9518
|
+
var PORT_MEMORY_PATH_ENV_VAR = "THEOKIT_PORT_MEMORY_PATH";
|
|
9519
|
+
function shouldUsePortMemoryPath() {
|
|
9520
|
+
const env = globalThis.process?.env;
|
|
9521
|
+
if (env === void 0) return false;
|
|
9522
|
+
const val = env[PORT_MEMORY_PATH_ENV_VAR];
|
|
9523
|
+
return val === "1" || val === "true";
|
|
9524
|
+
}
|
|
9525
|
+
function resolveMemoryProviderForLoop(consumerSupplied, defaultAdapter, portPathEnabled) {
|
|
9526
|
+
if (consumerSupplied !== void 0) return consumerSupplied;
|
|
9527
|
+
if (portPathEnabled) return defaultAdapter;
|
|
9528
|
+
return void 0;
|
|
9529
|
+
}
|
|
9530
|
+
function resolveMemoryToolsForLoop(legacyTools, portPathEnabled) {
|
|
9531
|
+
if (portPathEnabled) return void 0;
|
|
9532
|
+
return legacyTools;
|
|
9533
|
+
}
|
|
9534
|
+
function resolveActiveMemorySummaryForSend(legacySummary, portPathEnabled) {
|
|
9535
|
+
if (portPathEnabled) return void 0;
|
|
9536
|
+
return legacySummary;
|
|
9281
9537
|
}
|
|
9282
9538
|
|
|
9283
9539
|
// src/internal/runtime/session/agent-session.ts
|
|
9540
|
+
init_conversation_storage_fs();
|
|
9541
|
+
init_agent_session_store();
|
|
9284
9542
|
var DEFAULT_MAX_TURNS = 200;
|
|
9285
9543
|
var COMPACTION_CHECK_INTERVAL = 50;
|
|
9286
9544
|
var sessions = /* @__PURE__ */ new Map();
|
|
@@ -16357,6 +16615,9 @@ async function readProjectMcpServers(cwd) {
|
|
|
16357
16615
|
}
|
|
16358
16616
|
}
|
|
16359
16617
|
|
|
16618
|
+
// src/internal/runtime/local-agent/local-agent.ts
|
|
16619
|
+
init_local_agent_goal_extensions();
|
|
16620
|
+
|
|
16360
16621
|
// src/internal/runtime/local-agent/local-agent-invalidate.ts
|
|
16361
16622
|
async function applyDeferredInvalidation(agentId, pending, refresh) {
|
|
16362
16623
|
process.stderr.write(
|
|
@@ -17739,16 +18000,49 @@ async function localAgentUsePersonality(args) {
|
|
|
17739
18000
|
init_local_agent_plugins();
|
|
17740
18001
|
|
|
17741
18002
|
// src/internal/runtime/local-agent/local-agent-runtime-extensions.ts
|
|
17742
|
-
function
|
|
18003
|
+
function pausedReason(kind, threadId) {
|
|
18004
|
+
switch (kind) {
|
|
18005
|
+
case "none":
|
|
18006
|
+
return `no durable objective set for thread "${threadId}" \u2014 call setObjective() first`;
|
|
18007
|
+
case "inert":
|
|
18008
|
+
return `durable objective for thread "${threadId}" is inert \u2014 no judge resolved (set a judge to activate)`;
|
|
18009
|
+
case "exhausted":
|
|
18010
|
+
return `durable objective for thread "${threadId}" exhausted its run budget \u2014 raise maxRuns to resume`;
|
|
18011
|
+
}
|
|
18012
|
+
}
|
|
18013
|
+
function localAgentRunUntil(agent, goal, options, durable) {
|
|
17743
18014
|
async function* wrap() {
|
|
17744
18015
|
const { runUntilImpl: runUntilImpl2 } = await Promise.resolve().then(() => (init_run_until(), run_until_exports));
|
|
17745
|
-
const
|
|
17746
|
-
|
|
17747
|
-
|
|
17748
|
-
|
|
17749
|
-
|
|
18016
|
+
const buildDeps = async () => {
|
|
18017
|
+
const { judgeCallImpl: judgeCallImpl2 } = await Promise.resolve().then(() => (init_judge_call(), judge_call_exports));
|
|
18018
|
+
const { getAgentFacade: getAgentFacade2 } = await Promise.resolve().then(() => (init_agent_factory_registry(), agent_factory_registry_exports));
|
|
18019
|
+
const create = getAgentFacade2().create;
|
|
18020
|
+
return {
|
|
18021
|
+
judge: (ctx, opts) => judgeCallImpl2(ctx, opts, { create })
|
|
18022
|
+
};
|
|
17750
18023
|
};
|
|
17751
|
-
|
|
18024
|
+
if (goal !== void 0) {
|
|
18025
|
+
return yield* runUntilImpl2(agent, goal, options, await buildDeps());
|
|
18026
|
+
}
|
|
18027
|
+
const threadId = options?.threadId;
|
|
18028
|
+
if (durable === void 0 || threadId === void 0) {
|
|
18029
|
+
const reason = "runUntil() called with no goal and no threadId \u2014 nothing to resolve a durable objective from";
|
|
18030
|
+
yield { type: "status_change", status: "paused", reason };
|
|
18031
|
+
return { status: "paused", turnsUsed: 0, finalResponse: void 0 };
|
|
18032
|
+
}
|
|
18033
|
+
const { resolveDurableRun: resolveDurableRun2, persistDurableProgress: persistDurableProgress2 } = await Promise.resolve().then(() => (init_local_agent_goal_extensions(), local_agent_goal_extensions_exports));
|
|
18034
|
+
const resolved = await resolveDurableRun2(durable.handle, durable.goalConfig, threadId, options);
|
|
18035
|
+
if (resolved.kind !== "run") {
|
|
18036
|
+
yield {
|
|
18037
|
+
type: "status_change",
|
|
18038
|
+
status: "paused",
|
|
18039
|
+
reason: pausedReason(resolved.kind, threadId)
|
|
18040
|
+
};
|
|
18041
|
+
return { status: "paused", turnsUsed: 0, finalResponse: void 0 };
|
|
18042
|
+
}
|
|
18043
|
+
const result = yield* runUntilImpl2(agent, resolved.goal, resolved.options, await buildDeps());
|
|
18044
|
+
await persistDurableProgress2(durable.handle, threadId, result);
|
|
18045
|
+
return result;
|
|
17752
18046
|
}
|
|
17753
18047
|
return wrap();
|
|
17754
18048
|
}
|
|
@@ -17814,6 +18108,44 @@ function ponyfillAny(signals) {
|
|
|
17814
18108
|
return ctrl.signal;
|
|
17815
18109
|
}
|
|
17816
18110
|
|
|
18111
|
+
// src/internal/runtime/lifecycle/wrap-completion-check-run.ts
|
|
18112
|
+
function wrapRunWithCompletionCheck(args) {
|
|
18113
|
+
const check = args.completionCheck;
|
|
18114
|
+
if (check === void 0) return args.run;
|
|
18115
|
+
const compute = async () => {
|
|
18116
|
+
const result = await args.run.wait();
|
|
18117
|
+
if (result.status !== "finished" || result.result === void 0) return result;
|
|
18118
|
+
const judgeOpts = {};
|
|
18119
|
+
if (check.judgeModel !== void 0) judgeOpts.judgeModel = check.judgeModel;
|
|
18120
|
+
if (check.apiKey !== void 0) judgeOpts.apiKey = check.apiKey;
|
|
18121
|
+
const verdict = await args.deps.judge(
|
|
18122
|
+
{ goal: check.criteria, lastResponse: result.result },
|
|
18123
|
+
judgeOpts
|
|
18124
|
+
);
|
|
18125
|
+
const complete = !verdict.parseFailed && verdict.verdict === "done";
|
|
18126
|
+
emitRunEvent(args.onRunEvent, {
|
|
18127
|
+
type: "completion_check",
|
|
18128
|
+
complete,
|
|
18129
|
+
reason: verdict.reason
|
|
18130
|
+
});
|
|
18131
|
+
return {
|
|
18132
|
+
...result,
|
|
18133
|
+
completionCheck: { complete, reason: verdict.reason, parseFailed: verdict.parseFailed }
|
|
18134
|
+
};
|
|
18135
|
+
};
|
|
18136
|
+
let judged;
|
|
18137
|
+
const wrappedWait = () => {
|
|
18138
|
+
judged ??= compute();
|
|
18139
|
+
return judged;
|
|
18140
|
+
};
|
|
18141
|
+
return new Proxy(args.run, {
|
|
18142
|
+
get(target, prop, receiver) {
|
|
18143
|
+
if (prop === "wait") return wrappedWait;
|
|
18144
|
+
return Reflect.get(target, prop, receiver);
|
|
18145
|
+
}
|
|
18146
|
+
});
|
|
18147
|
+
}
|
|
18148
|
+
|
|
17817
18149
|
// src/internal/runtime/processors/run-processors.ts
|
|
17818
18150
|
var ProcessorAbort = class {
|
|
17819
18151
|
constructor(processorId, reason) {
|
|
@@ -17953,6 +18285,9 @@ function wrapRunWithOutputProcessors(args) {
|
|
|
17953
18285
|
});
|
|
17954
18286
|
}
|
|
17955
18287
|
|
|
18288
|
+
// src/internal/runtime/local-agent/local-agent-send.ts
|
|
18289
|
+
init_local_agent_goal_extensions();
|
|
18290
|
+
|
|
17956
18291
|
// src/internal/runtime/local-agent/local-agent-memory-hooks.ts
|
|
17957
18292
|
var DEFAULT_MAX_RECALL_BYTES = 16e3;
|
|
17958
18293
|
async function applyPreUserSendHook(args) {
|
|
@@ -18068,6 +18403,11 @@ async function executeSendLocked(inputs, message, options) {
|
|
|
18068
18403
|
memoryFacts,
|
|
18069
18404
|
activeMemorySummary
|
|
18070
18405
|
);
|
|
18406
|
+
const projectedSystemPrompt = await projectCurrentObjective(
|
|
18407
|
+
inputs.storageHandle,
|
|
18408
|
+
options.objectiveThreadId,
|
|
18409
|
+
assembledSystemPrompt
|
|
18410
|
+
);
|
|
18071
18411
|
const composedOptions = {
|
|
18072
18412
|
...options,
|
|
18073
18413
|
signal: anySignal([options.signal, inputs.lifecycleAbortController.signal])
|
|
@@ -18075,7 +18415,7 @@ async function executeSendLocked(inputs, message, options) {
|
|
|
18075
18415
|
const run = await inputs.dispatchRun(
|
|
18076
18416
|
adaptedMessage,
|
|
18077
18417
|
composedOptions,
|
|
18078
|
-
|
|
18418
|
+
projectedSystemPrompt,
|
|
18079
18419
|
memoryFacts,
|
|
18080
18420
|
priorMessages,
|
|
18081
18421
|
memoryTools,
|
|
@@ -18088,18 +18428,43 @@ async function executeSendLocked(inputs, message, options) {
|
|
|
18088
18428
|
agentId: inputs.agentId,
|
|
18089
18429
|
onRunEvent: options.onRunEvent
|
|
18090
18430
|
}) : run;
|
|
18091
|
-
|
|
18431
|
+
const hookedRun = wrapRunWithPostReplyHook({
|
|
18092
18432
|
pluginManager: inputs.pluginManagerCode,
|
|
18093
18433
|
agentId: inputs.agentId,
|
|
18094
18434
|
options: inputs.options,
|
|
18095
18435
|
run: processedRun,
|
|
18096
18436
|
userText
|
|
18097
18437
|
});
|
|
18438
|
+
return wrapRunWithCompletionCheck({
|
|
18439
|
+
run: hookedRun,
|
|
18440
|
+
completionCheck: options.completionCheck,
|
|
18441
|
+
onRunEvent: options.onRunEvent,
|
|
18442
|
+
deps: buildCompletionCheckDeps()
|
|
18443
|
+
});
|
|
18444
|
+
}
|
|
18445
|
+
function buildCompletionCheckDeps() {
|
|
18446
|
+
return {
|
|
18447
|
+
judge: async (ctx, opts) => {
|
|
18448
|
+
const { judgeCallImpl: judgeCallImpl2 } = await Promise.resolve().then(() => (init_judge_call(), judge_call_exports));
|
|
18449
|
+
const { getAgentFacade: getAgentFacade2 } = await Promise.resolve().then(() => (init_agent_factory_registry(), agent_factory_registry_exports));
|
|
18450
|
+
return judgeCallImpl2(ctx, opts, { create: getAgentFacade2().create });
|
|
18451
|
+
}
|
|
18452
|
+
};
|
|
18098
18453
|
}
|
|
18099
18454
|
function readMemoryForSend(workspaceCwd, memoryConfig) {
|
|
18100
18455
|
if (memoryConfig?.enabled !== true) return Promise.resolve([]);
|
|
18101
18456
|
return safeCall(() => readMemoryFacts(workspaceCwd, memoryConfig), [], "memory read");
|
|
18102
18457
|
}
|
|
18458
|
+
async function projectCurrentObjective(storageHandle, objectiveThreadId, assembled) {
|
|
18459
|
+
if (objectiveThreadId === void 0) return assembled;
|
|
18460
|
+
const objective = await safeCall(
|
|
18461
|
+
() => resolveCurrentObjectiveText(storageHandle, objectiveThreadId),
|
|
18462
|
+
void 0,
|
|
18463
|
+
"objective projection"
|
|
18464
|
+
);
|
|
18465
|
+
if (objective === void 0) return assembled;
|
|
18466
|
+
return formatObjectiveProjection(objective, assembled);
|
|
18467
|
+
}
|
|
18103
18468
|
|
|
18104
18469
|
// src/internal/runtime/local-agent/local-agent-task-wrap.ts
|
|
18105
18470
|
init_registry();
|
|
@@ -18462,20 +18827,33 @@ var LocalAgent = class {
|
|
|
18462
18827
|
...opts !== void 0 ? { opts } : {}
|
|
18463
18828
|
});
|
|
18464
18829
|
}
|
|
18830
|
+
// biome-ignore format: G8 budget — artifact stubs (local agents have no artifacts); kept 1-line each.
|
|
18465
18831
|
listArtifacts() {
|
|
18466
18832
|
return Promise.resolve([]);
|
|
18467
18833
|
}
|
|
18834
|
+
// biome-ignore format: G8 budget — see listArtifacts.
|
|
18468
18835
|
downloadArtifact(_path) {
|
|
18469
|
-
return Promise.reject(
|
|
18470
|
-
new UnsupportedRunOperationError(
|
|
18471
|
-
"Artifacts are not supported for local agents",
|
|
18472
|
-
"downloadArtifact"
|
|
18473
|
-
)
|
|
18474
|
-
);
|
|
18836
|
+
return Promise.reject(new UnsupportedRunOperationError("Artifacts are not supported for local agents", "downloadArtifact"));
|
|
18475
18837
|
}
|
|
18476
18838
|
// biome-ignore format: G8 budget — both methods delegate to `local-agent-runtime-extensions.ts`; signatures kept as 1-line each.
|
|
18477
18839
|
runUntil(goal, options) {
|
|
18478
|
-
return localAgentRunUntil(this, goal, options);
|
|
18840
|
+
return localAgentRunUntil(this, goal, options, { handle: this.storageHandle(), goalConfig: this.options.goal });
|
|
18841
|
+
}
|
|
18842
|
+
// biome-ignore format: SE33 G8 budget — objective methods delegate to `local-agent-goal-extensions.ts`.
|
|
18843
|
+
setObjective(objective, opts) {
|
|
18844
|
+
return localAgentSetObjective(this.storageHandle(), objective, opts);
|
|
18845
|
+
}
|
|
18846
|
+
// biome-ignore format: SE33 G8 budget — see setObjective above.
|
|
18847
|
+
getObjective(opts) {
|
|
18848
|
+
return localAgentGetObjective(this.storageHandle(), opts);
|
|
18849
|
+
}
|
|
18850
|
+
// biome-ignore format: SE33 G8 budget — see setObjective above.
|
|
18851
|
+
updateObjectiveOptions(opts) {
|
|
18852
|
+
return localAgentUpdateObjectiveOptions(this.storageHandle(), opts);
|
|
18853
|
+
}
|
|
18854
|
+
// biome-ignore format: SE33 G8 budget — see setObjective above.
|
|
18855
|
+
clearObjective(opts) {
|
|
18856
|
+
return localAgentClearObjective(this.storageHandle(), opts);
|
|
18479
18857
|
}
|
|
18480
18858
|
// biome-ignore format: G8 budget — see runUntil comment above.
|
|
18481
18859
|
fork(options) {
|
|
@@ -20059,12 +20437,17 @@ var EventBus = class {
|
|
|
20059
20437
|
|
|
20060
20438
|
// src/index.ts
|
|
20061
20439
|
init_generate_object();
|
|
20440
|
+
init_conversation_storage_fs();
|
|
20062
20441
|
|
|
20063
20442
|
// src/internal/persistence/conversation-storage-memory.ts
|
|
20443
|
+
init_pagination();
|
|
20444
|
+
init_session_meta();
|
|
20064
20445
|
var InMemoryConversationStorage = class {
|
|
20065
20446
|
#store = /* @__PURE__ */ new Map();
|
|
20066
20447
|
// SE4 — session-level metadata (title/tag), kept beside the transcript.
|
|
20067
20448
|
#meta = /* @__PURE__ */ new Map();
|
|
20449
|
+
// SE33 — durable thread-scoped objective, kept beside the transcript.
|
|
20450
|
+
#objectives = /* @__PURE__ */ new Map();
|
|
20068
20451
|
async getMessages(conversationId, opts) {
|
|
20069
20452
|
const existing = this.#store.get(conversationId);
|
|
20070
20453
|
return existing === void 0 ? [] : paginate(existing.slice(), opts);
|
|
@@ -20094,18 +20477,21 @@ var InMemoryConversationStorage = class {
|
|
|
20094
20477
|
async deleteConversation(conversationId) {
|
|
20095
20478
|
this.#store.delete(conversationId);
|
|
20096
20479
|
this.#meta.delete(conversationId);
|
|
20480
|
+
this.#objectives.delete(conversationId);
|
|
20097
20481
|
}
|
|
20098
20482
|
async deleteScope(prefix) {
|
|
20099
20483
|
let n = 0;
|
|
20100
|
-
|
|
20101
|
-
|
|
20102
|
-
|
|
20103
|
-
|
|
20104
|
-
|
|
20105
|
-
|
|
20106
|
-
|
|
20107
|
-
|
|
20108
|
-
|
|
20484
|
+
const ids = /* @__PURE__ */ new Set([
|
|
20485
|
+
...this.#store.keys(),
|
|
20486
|
+
...this.#meta.keys(),
|
|
20487
|
+
...this.#objectives.keys()
|
|
20488
|
+
]);
|
|
20489
|
+
for (const id of ids) {
|
|
20490
|
+
if (!id.startsWith(prefix)) continue;
|
|
20491
|
+
this.#store.delete(id);
|
|
20492
|
+
this.#meta.delete(id);
|
|
20493
|
+
this.#objectives.delete(id);
|
|
20494
|
+
n += 1;
|
|
20109
20495
|
}
|
|
20110
20496
|
return n;
|
|
20111
20497
|
}
|
|
@@ -20122,9 +20508,27 @@ var InMemoryConversationStorage = class {
|
|
|
20122
20508
|
const current = this.#meta.get(conversationId) ?? {};
|
|
20123
20509
|
this.#meta.set(conversationId, applyMetaPatch(current, patch));
|
|
20124
20510
|
}
|
|
20511
|
+
async getObjectiveRecord(conversationId) {
|
|
20512
|
+
const rec = this.#objectives.get(conversationId);
|
|
20513
|
+
return rec === void 0 ? void 0 : { ...rec };
|
|
20514
|
+
}
|
|
20515
|
+
async setObjectiveRecord(conversationId, record) {
|
|
20516
|
+
if (record === null) this.#objectives.delete(conversationId);
|
|
20517
|
+
else this.#objectives.set(conversationId, { ...record });
|
|
20518
|
+
}
|
|
20519
|
+
// SE33 (HIGH-1 fix) — atomic in the single-threaded runtime: the get→mutate→set
|
|
20520
|
+
// runs synchronously, so there is no interleaving window for a concurrent frame.
|
|
20521
|
+
async updateObjectiveRecord(conversationId, mutate) {
|
|
20522
|
+
const current = this.#objectives.get(conversationId);
|
|
20523
|
+
const next = mutate(current === void 0 ? void 0 : { ...current });
|
|
20524
|
+
if (next === void 0) return;
|
|
20525
|
+
if (next === null) this.#objectives.delete(conversationId);
|
|
20526
|
+
else this.#objectives.set(conversationId, { ...next });
|
|
20527
|
+
}
|
|
20125
20528
|
async dispose() {
|
|
20126
20529
|
this.#store.clear();
|
|
20127
20530
|
this.#meta.clear();
|
|
20531
|
+
this.#objectives.clear();
|
|
20128
20532
|
}
|
|
20129
20533
|
};
|
|
20130
20534
|
|