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