@rowan-agent/agent 0.5.6 → 0.6.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/README.md +256 -321
- package/dist/index.d.ts +666 -886
- package/dist/index.js +2213 -499
- package/package.json +4 -5
package/dist/index.js
CHANGED
|
@@ -380,9 +380,9 @@ function createExtensionAPI(hooks, _extensionPath, options, runtime, eventBus) {
|
|
|
380
380
|
}
|
|
381
381
|
|
|
382
382
|
// src/harness/phases/loader.ts
|
|
383
|
-
import { existsSync
|
|
383
|
+
import { existsSync, statSync } from "fs";
|
|
384
384
|
import { readdir } from "fs/promises";
|
|
385
|
-
import { dirname as dirname2, join
|
|
385
|
+
import { dirname as dirname2, join, resolve } from "path";
|
|
386
386
|
|
|
387
387
|
// src/harness/loader.ts
|
|
388
388
|
import { readFile } from "fs/promises";
|
|
@@ -447,181 +447,8 @@ function inferResourceName(path, markerFile) {
|
|
|
447
447
|
return extension ? file.slice(0, -extension.length) : file;
|
|
448
448
|
}
|
|
449
449
|
|
|
450
|
-
// src/harness/
|
|
451
|
-
import {
|
|
452
|
-
import { readFile as readFile2 } from "fs/promises";
|
|
453
|
-
import { join } from "path";
|
|
454
|
-
import { parse as parseYaml } from "yaml";
|
|
455
|
-
import { registerModel } from "@rowan-agent/models";
|
|
456
|
-
var DEFAULT_REASONING = false;
|
|
457
|
-
var DEFAULT_INPUT = ["text"];
|
|
458
|
-
var DEFAULT_CONTEXT_WINDOW = 128e3;
|
|
459
|
-
var DEFAULT_MAX_TOKENS = 16384;
|
|
460
|
-
var DEFAULT_COST = { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 };
|
|
461
|
-
var DEFAULT_TIMEOUT_MS = 6e4;
|
|
462
|
-
var ENV_VAR_PATTERN = /\$\{([^}]+)\}/g;
|
|
463
|
-
function interpolateEnvVars(value) {
|
|
464
|
-
return value.replace(ENV_VAR_PATTERN, (match, varName) => {
|
|
465
|
-
const resolved = process.env[varName];
|
|
466
|
-
if (resolved === void 0 || resolved === "") {
|
|
467
|
-
throw new Error(
|
|
468
|
-
`Environment variable "${varName}" is not set but is referenced in config.yaml via "${match}".`
|
|
469
|
-
);
|
|
470
|
-
}
|
|
471
|
-
return resolved;
|
|
472
|
-
});
|
|
473
|
-
}
|
|
474
|
-
var CONFIG_CANDIDATES = ["config.yaml", "config.yml"];
|
|
475
|
-
async function loadConfigFile(workspace) {
|
|
476
|
-
let configPath;
|
|
477
|
-
let configFilename;
|
|
478
|
-
for (const candidate of CONFIG_CANDIDATES) {
|
|
479
|
-
const p = join(workspace.rowanDir, candidate);
|
|
480
|
-
if (existsSync(p)) {
|
|
481
|
-
configPath = p;
|
|
482
|
-
configFilename = candidate;
|
|
483
|
-
break;
|
|
484
|
-
}
|
|
485
|
-
}
|
|
486
|
-
if (!configPath || !configFilename) {
|
|
487
|
-
return void 0;
|
|
488
|
-
}
|
|
489
|
-
const raw = await readFile2(configPath, "utf8");
|
|
490
|
-
let parsed;
|
|
491
|
-
try {
|
|
492
|
-
parsed = parseYaml(raw);
|
|
493
|
-
} catch (error) {
|
|
494
|
-
const message = error instanceof Error ? error.message : String(error);
|
|
495
|
-
throw new Error(`Failed to parse .rowan/${configFilename}: ${message}`);
|
|
496
|
-
}
|
|
497
|
-
if (parsed === null || parsed === void 0) {
|
|
498
|
-
return void 0;
|
|
499
|
-
}
|
|
500
|
-
return validateConfigFile(parsed, configFilename);
|
|
501
|
-
}
|
|
502
|
-
function validateConfigFile(parsed, configFilename) {
|
|
503
|
-
if (typeof parsed !== "object" || parsed === null) {
|
|
504
|
-
throw new Error(`.rowan/${configFilename} must be a YAML object at the top level.`);
|
|
505
|
-
}
|
|
506
|
-
const obj = parsed;
|
|
507
|
-
const providers = obj.providers;
|
|
508
|
-
if (!Array.isArray(providers)) {
|
|
509
|
-
throw new Error(`.rowan/${configFilename} must have a "providers" array.`);
|
|
510
|
-
}
|
|
511
|
-
const validatedProviders = providers.map((p, i) => {
|
|
512
|
-
if (typeof p !== "object" || p === null) {
|
|
513
|
-
throw new Error(`providers[${i}] must be an object.`);
|
|
514
|
-
}
|
|
515
|
-
const provider = p;
|
|
516
|
-
if (typeof provider.id !== "string" || !provider.id) {
|
|
517
|
-
throw new Error(`providers[${i}].id is required and must be a non-empty string.`);
|
|
518
|
-
}
|
|
519
|
-
if (typeof provider.baseUrl !== "string" || !provider.baseUrl) {
|
|
520
|
-
throw new Error(`providers[${i}].baseUrl is required and must be a non-empty string.`);
|
|
521
|
-
}
|
|
522
|
-
if (typeof provider.apiKey !== "string" || !provider.apiKey) {
|
|
523
|
-
throw new Error(`providers[${i}].apiKey is required and must be a non-empty string.`);
|
|
524
|
-
}
|
|
525
|
-
if (typeof provider.protocol !== "string" || !provider.protocol) {
|
|
526
|
-
throw new Error(`providers[${i}].protocol is required and must be a non-empty string.`);
|
|
527
|
-
}
|
|
528
|
-
if (!Array.isArray(provider.models) || provider.models.length === 0) {
|
|
529
|
-
throw new Error(`providers[${i}].models must be a non-empty array.`);
|
|
530
|
-
}
|
|
531
|
-
const interpolatedApiKey = interpolateEnvVars(provider.apiKey);
|
|
532
|
-
const validatedModels = provider.models.map((m, j) => {
|
|
533
|
-
if (typeof m !== "object" || m === null) {
|
|
534
|
-
throw new Error(`providers[${i}].models[${j}] must be an object.`);
|
|
535
|
-
}
|
|
536
|
-
const model = m;
|
|
537
|
-
if (typeof model.id !== "string" || !model.id) {
|
|
538
|
-
throw new Error(`providers[${i}].models[${j}].id is required and must be a non-empty string.`);
|
|
539
|
-
}
|
|
540
|
-
return model;
|
|
541
|
-
});
|
|
542
|
-
return {
|
|
543
|
-
id: provider.id,
|
|
544
|
-
baseUrl: provider.baseUrl,
|
|
545
|
-
apiKey: interpolatedApiKey,
|
|
546
|
-
protocol: provider.protocol,
|
|
547
|
-
...typeof provider.name === "string" ? { name: provider.name } : {},
|
|
548
|
-
...typeof provider.timeoutMs === "number" ? { timeoutMs: provider.timeoutMs } : {},
|
|
549
|
-
...typeof provider.maxRetries === "number" ? { maxRetries: provider.maxRetries } : {},
|
|
550
|
-
...typeof provider.retryDelayMs === "number" ? { retryDelayMs: provider.retryDelayMs } : {},
|
|
551
|
-
...provider.headers && typeof provider.headers === "object" ? { headers: provider.headers } : {},
|
|
552
|
-
models: validatedModels
|
|
553
|
-
};
|
|
554
|
-
});
|
|
555
|
-
const result = { providers: validatedProviders };
|
|
556
|
-
if (obj.model && typeof obj.model === "object") {
|
|
557
|
-
const modelRef = obj.model;
|
|
558
|
-
if (typeof modelRef.provider !== "string" || typeof modelRef.id !== "string") {
|
|
559
|
-
throw new Error(`.rowan/${configFilename} "model" must have "provider" and "id" string fields.`);
|
|
560
|
-
}
|
|
561
|
-
result.model = { provider: modelRef.provider, id: modelRef.id };
|
|
562
|
-
}
|
|
563
|
-
if (obj.logLevel !== void 0) {
|
|
564
|
-
const valid = ["debug", "info", "warn", "error", "silent"];
|
|
565
|
-
if (typeof obj.logLevel !== "string" || !valid.includes(obj.logLevel)) {
|
|
566
|
-
throw new Error(`.rowan/${configFilename} "logLevel" must be one of: ${valid.join(", ")}.`);
|
|
567
|
-
}
|
|
568
|
-
result.logLevel = obj.logLevel;
|
|
569
|
-
}
|
|
570
|
-
return result;
|
|
571
|
-
}
|
|
572
|
-
function resolveDefaultModel(config) {
|
|
573
|
-
if (config.model) {
|
|
574
|
-
return { provider: config.model.provider, id: config.model.id };
|
|
575
|
-
}
|
|
576
|
-
for (const provider of config.providers) {
|
|
577
|
-
for (const model of provider.models) {
|
|
578
|
-
if (model.primary) {
|
|
579
|
-
return { provider: provider.id, id: model.id };
|
|
580
|
-
}
|
|
581
|
-
}
|
|
582
|
-
}
|
|
583
|
-
for (const provider of config.providers) {
|
|
584
|
-
if (provider.models.length > 0) {
|
|
585
|
-
return { provider: provider.id, id: provider.models[0].id };
|
|
586
|
-
}
|
|
587
|
-
}
|
|
588
|
-
return void 0;
|
|
589
|
-
}
|
|
590
|
-
function registerConfigModels(config) {
|
|
591
|
-
for (const provider of config.providers) {
|
|
592
|
-
for (const fileModel of provider.models) {
|
|
593
|
-
const model = {
|
|
594
|
-
id: fileModel.id,
|
|
595
|
-
name: fileModel.name ?? fileModel.id,
|
|
596
|
-
protocol: provider.protocol,
|
|
597
|
-
provider: provider.id,
|
|
598
|
-
baseUrl: provider.baseUrl,
|
|
599
|
-
reasoning: fileModel.reasoning ?? DEFAULT_REASONING,
|
|
600
|
-
input: fileModel.input ?? DEFAULT_INPUT,
|
|
601
|
-
cost: { ...DEFAULT_COST, ...fileModel.cost },
|
|
602
|
-
contextWindow: fileModel.contextWindow ?? DEFAULT_CONTEXT_WINDOW,
|
|
603
|
-
maxTokens: fileModel.maxTokens ?? DEFAULT_MAX_TOKENS,
|
|
604
|
-
apiKey: provider.apiKey,
|
|
605
|
-
timeoutMs: provider.timeoutMs ?? DEFAULT_TIMEOUT_MS,
|
|
606
|
-
...provider.maxRetries !== void 0 ? { maxRetries: provider.maxRetries } : {},
|
|
607
|
-
...provider.retryDelayMs !== void 0 ? { retryDelayMs: provider.retryDelayMs } : {},
|
|
608
|
-
...provider.headers ? { headers: provider.headers } : {}
|
|
609
|
-
};
|
|
610
|
-
registerModel(model);
|
|
611
|
-
}
|
|
612
|
-
}
|
|
613
|
-
}
|
|
614
|
-
function parseModelRef(input) {
|
|
615
|
-
if (!input) return void 0;
|
|
616
|
-
const slashIndex = input.indexOf("/");
|
|
617
|
-
if (slashIndex === -1) {
|
|
618
|
-
return { provider: "*", id: input };
|
|
619
|
-
}
|
|
620
|
-
return {
|
|
621
|
-
provider: input.slice(0, slashIndex),
|
|
622
|
-
id: input.slice(slashIndex + 1)
|
|
623
|
-
};
|
|
624
|
-
}
|
|
450
|
+
// src/harness/phases/loader.ts
|
|
451
|
+
import { parseModelRef } from "@rowan-agent/models";
|
|
625
452
|
|
|
626
453
|
// src/harness/context/resource-formatter.ts
|
|
627
454
|
function escapeXml(value) {
|
|
@@ -725,7 +552,7 @@ function buildPhaseDirectiveMessage(phase, output, toolUseId) {
|
|
|
725
552
|
var PHASE_MARKER = "PHASE.md";
|
|
726
553
|
function resolvePhasePath(input) {
|
|
727
554
|
const resolved = resolve(input);
|
|
728
|
-
return
|
|
555
|
+
return existsSync(resolved) && statSync(resolved).isDirectory() ? join(resolved, PHASE_MARKER) : resolved;
|
|
729
556
|
}
|
|
730
557
|
async function loadPhase(targetPath) {
|
|
731
558
|
const resolved = resolvePhasePath(targetPath);
|
|
@@ -772,18 +599,15 @@ function readPhaseContent(phase) {
|
|
|
772
599
|
baseDir: phase.baseDir
|
|
773
600
|
});
|
|
774
601
|
}
|
|
775
|
-
function resolvePhaseLoadTarget(targetPath) {
|
|
776
|
-
return typeof targetPath === "string" ? targetPath : join2(targetPath.rowanDir, "phases");
|
|
777
|
-
}
|
|
778
602
|
async function loadPhases(targetPath) {
|
|
779
603
|
const phases = /* @__PURE__ */ new Map();
|
|
780
|
-
const phasesDir = resolve(
|
|
781
|
-
if (
|
|
604
|
+
const phasesDir = resolve(targetPath);
|
|
605
|
+
if (existsSync(phasesDir) && statSync(phasesDir).isFile()) {
|
|
782
606
|
const phase = await loadPhase(phasesDir);
|
|
783
607
|
phases.set(phase.id, phase);
|
|
784
608
|
return { phases, entryPhaseId: null };
|
|
785
609
|
}
|
|
786
|
-
if (
|
|
610
|
+
if (existsSync(join(phasesDir, PHASE_MARKER))) {
|
|
787
611
|
const phase = await loadPhase(phasesDir);
|
|
788
612
|
phases.set(phase.id, phase);
|
|
789
613
|
return { phases, entryPhaseId: null };
|
|
@@ -791,8 +615,8 @@ async function loadPhases(targetPath) {
|
|
|
791
615
|
const entries = await readdir(phasesDir, { withFileTypes: true });
|
|
792
616
|
for (const entry of [...entries].sort((a, b) => a.name.localeCompare(b.name))) {
|
|
793
617
|
if (!entry.isDirectory()) continue;
|
|
794
|
-
const phaseFile =
|
|
795
|
-
if (!
|
|
618
|
+
const phaseFile = join(phasesDir, entry.name, PHASE_MARKER);
|
|
619
|
+
if (!existsSync(phaseFile)) continue;
|
|
796
620
|
try {
|
|
797
621
|
const phase = await loadPhase(phaseFile);
|
|
798
622
|
phases.set(phase.id, phase);
|
|
@@ -805,8 +629,8 @@ async function loadPhases(targetPath) {
|
|
|
805
629
|
async function discoverPhaseCode(baseDir) {
|
|
806
630
|
const candidates = ["index.ts", "index.js"];
|
|
807
631
|
for (const candidate of candidates) {
|
|
808
|
-
const fullPath =
|
|
809
|
-
if (
|
|
632
|
+
const fullPath = join(baseDir, candidate);
|
|
633
|
+
if (existsSync(fullPath)) {
|
|
810
634
|
return fullPath;
|
|
811
635
|
}
|
|
812
636
|
}
|
|
@@ -815,7 +639,9 @@ async function discoverPhaseCode(baseDir) {
|
|
|
815
639
|
async function loadPhaseCode(codePath) {
|
|
816
640
|
const { createJiti: createJiti2 } = await import("jiti");
|
|
817
641
|
const jiti = createJiti2(import.meta.url, {
|
|
818
|
-
|
|
642
|
+
fsCache: false,
|
|
643
|
+
moduleCache: false,
|
|
644
|
+
tryNative: false
|
|
819
645
|
});
|
|
820
646
|
const mod = await jiti.import(codePath, { default: true });
|
|
821
647
|
const fn = typeof mod === "function" ? mod : mod?.default;
|
|
@@ -827,19 +653,6 @@ async function loadPhaseCode(codePath) {
|
|
|
827
653
|
}
|
|
828
654
|
throw new Error(`Phase code at "${codePath}" must export a default function or a run() function.`);
|
|
829
655
|
}
|
|
830
|
-
async function reloadPhases(registry) {
|
|
831
|
-
for (const [id, phase] of registry.phases) {
|
|
832
|
-
if (!phase.filePath) {
|
|
833
|
-
continue;
|
|
834
|
-
}
|
|
835
|
-
try {
|
|
836
|
-
const fresh = await loadPhase(phase.filePath);
|
|
837
|
-
registry.phases.set(id, fresh);
|
|
838
|
-
} catch (error) {
|
|
839
|
-
console.warn(`Failed to reload phase "${id}":`, error);
|
|
840
|
-
}
|
|
841
|
-
}
|
|
842
|
-
}
|
|
843
656
|
|
|
844
657
|
// src/harness/phases/default.ts
|
|
845
658
|
var DEFAULT_PHASE_ID = "default";
|
|
@@ -855,119 +668,16 @@ function createDefaultPhase() {
|
|
|
855
668
|
}
|
|
856
669
|
|
|
857
670
|
// src/harness/tools/index.ts
|
|
858
|
-
import { mkdir, readFile as
|
|
859
|
-
import { basename as basename2, dirname as
|
|
671
|
+
import { mkdir, readFile as readFile2, stat, writeFile } from "fs/promises";
|
|
672
|
+
import { basename as basename2, dirname as dirname3, isAbsolute as isAbsolute2, relative as relative2, resolve as resolve3, sep as sep2 } from "path";
|
|
860
673
|
import Type2 from "typebox";
|
|
861
674
|
import Schema from "typebox/schema";
|
|
862
675
|
|
|
863
|
-
// src/harness/
|
|
864
|
-
import {
|
|
865
|
-
import { homedir } from "os";
|
|
866
|
-
import { dirname as dirname3, isAbsolute, join as join3, parse, relative, resolve as resolve2, sep } from "path";
|
|
867
|
-
var WORKSPACE_ENV = "ROWAN_WORKSPACE";
|
|
868
|
-
var BINARY_WORKSPACE_DIR = ".rowan";
|
|
869
|
-
function nonEmptyEnv(env, key) {
|
|
870
|
-
const value = env[key]?.trim();
|
|
871
|
-
return value ? value : void 0;
|
|
872
|
-
}
|
|
873
|
-
function resolveUserPath(path, homeDir) {
|
|
874
|
-
if (path === "~") {
|
|
875
|
-
return homeDir;
|
|
876
|
-
}
|
|
877
|
-
if (path.startsWith("~/") || path.startsWith("~\\")) {
|
|
878
|
-
return join3(homeDir, path.slice(2));
|
|
879
|
-
}
|
|
880
|
-
return resolve2(path);
|
|
881
|
-
}
|
|
882
|
-
function isSourceWorkspaceRoot(path) {
|
|
883
|
-
const packagePath = join3(path, "package.json");
|
|
884
|
-
if (!existsSync3(packagePath)) {
|
|
885
|
-
return false;
|
|
886
|
-
}
|
|
887
|
-
try {
|
|
888
|
-
const manifest = JSON.parse(readFileSync(packagePath, "utf8"));
|
|
889
|
-
return manifest.name === "rowan-agent" || Array.isArray(manifest.workspaces);
|
|
890
|
-
} catch {
|
|
891
|
-
return false;
|
|
892
|
-
}
|
|
893
|
-
}
|
|
894
|
-
function findSourceWorkspaceRoot(startDir = process.cwd()) {
|
|
895
|
-
let current = resolve2(startDir);
|
|
896
|
-
const { root } = parse(current);
|
|
897
|
-
while (true) {
|
|
898
|
-
if (isSourceWorkspaceRoot(current)) {
|
|
899
|
-
return current;
|
|
900
|
-
}
|
|
901
|
-
if (current === root) {
|
|
902
|
-
return resolve2(startDir);
|
|
903
|
-
}
|
|
904
|
-
current = dirname3(current);
|
|
905
|
-
}
|
|
906
|
-
}
|
|
907
|
-
function defaultWorkspaceStartDir(options) {
|
|
908
|
-
if (options.cwd) {
|
|
909
|
-
return options.cwd;
|
|
910
|
-
}
|
|
911
|
-
return process.cwd();
|
|
912
|
-
}
|
|
913
|
-
function resolveWorkspaceRoot(options = {}) {
|
|
914
|
-
const env = options.env ?? process.env;
|
|
915
|
-
const homeDir = options.homeDir ?? homedir();
|
|
916
|
-
const override = nonEmptyEnv(env, WORKSPACE_ENV);
|
|
917
|
-
if (override) {
|
|
918
|
-
return resolveUserPath(override, homeDir);
|
|
919
|
-
}
|
|
920
|
-
return findSourceWorkspaceRoot(defaultWorkspaceStartDir(options));
|
|
921
|
-
}
|
|
922
|
-
function resolveWorkspacePaths(options = {}) {
|
|
923
|
-
const cwd = resolveWorkspaceRoot(options);
|
|
924
|
-
return {
|
|
925
|
-
cwd,
|
|
926
|
-
rowanDir: resolveProjectRowanDir(cwd, options.rowanDir)
|
|
927
|
-
};
|
|
928
|
-
}
|
|
929
|
-
function resolveProjectRowanDir(cwd, rowanDir = BINARY_WORKSPACE_DIR) {
|
|
930
|
-
const inputPath = rowanDir.trim() || BINARY_WORKSPACE_DIR;
|
|
931
|
-
if (isAbsolute(inputPath)) {
|
|
932
|
-
throw new Error(`Project Rowan dir must be a relative path: ${rowanDir}`);
|
|
933
|
-
}
|
|
934
|
-
return resolveWorkspacePath({ root: cwd }, inputPath).absolutePath;
|
|
935
|
-
}
|
|
936
|
-
function resolveInWorkspace(path, rootOrPaths) {
|
|
937
|
-
if (path === "~" || path.startsWith("~/") || path.startsWith("~\\")) {
|
|
938
|
-
return resolveUserPath(path, homedir());
|
|
939
|
-
}
|
|
940
|
-
if (isAbsolute(path)) {
|
|
941
|
-
return path;
|
|
942
|
-
}
|
|
943
|
-
const root = typeof rootOrPaths === "string" ? rootOrPaths : rootOrPaths.cwd;
|
|
944
|
-
return resolve2(root, path);
|
|
945
|
-
}
|
|
676
|
+
// src/harness/path.ts
|
|
677
|
+
import { isAbsolute, relative, resolve as resolve2, sep } from "path";
|
|
946
678
|
function normalizeRelativePath(path) {
|
|
947
679
|
return path.split(sep).join("/");
|
|
948
680
|
}
|
|
949
|
-
function normalizeWorkspaceInputPath(path = ".") {
|
|
950
|
-
const trimmed = path.trim();
|
|
951
|
-
if (!trimmed || trimmed === "/" || trimmed === "\\") {
|
|
952
|
-
return ".";
|
|
953
|
-
}
|
|
954
|
-
return path;
|
|
955
|
-
}
|
|
956
|
-
function resolveWorkspacePath(context, path = ".") {
|
|
957
|
-
const root = resolve2(context.root);
|
|
958
|
-
const inputPath = normalizeWorkspaceInputPath(path);
|
|
959
|
-
const absolutePath = resolve2(root, inputPath);
|
|
960
|
-
const relativePath = relative(root, absolutePath);
|
|
961
|
-
if (relativePath === ".." || relativePath.startsWith(`..${sep}`) || isAbsolute(relativePath)) {
|
|
962
|
-
throw new Error(`Path escapes workspace root: ${path}`);
|
|
963
|
-
}
|
|
964
|
-
return {
|
|
965
|
-
root,
|
|
966
|
-
inputPath,
|
|
967
|
-
absolutePath,
|
|
968
|
-
relativePath: normalizeRelativePath(relativePath || ".")
|
|
969
|
-
};
|
|
970
|
-
}
|
|
971
681
|
|
|
972
682
|
// src/harness/tools/route-tool.ts
|
|
973
683
|
import Type from "typebox";
|
|
@@ -1310,7 +1020,7 @@ function createReadTool(context) {
|
|
|
1310
1020
|
error: `Not a file: ${resolved.relativePath}`
|
|
1311
1021
|
});
|
|
1312
1022
|
}
|
|
1313
|
-
const bytes = await
|
|
1023
|
+
const bytes = await readFile2(resolved.absolutePath);
|
|
1314
1024
|
const sliced = bytes.subarray(0, maxBytes);
|
|
1315
1025
|
const text = new TextDecoder().decode(sliced);
|
|
1316
1026
|
const resourceType = parsed.type ?? detectResourceType(resolved.absolutePath);
|
|
@@ -1320,7 +1030,7 @@ function createReadTool(context) {
|
|
|
1320
1030
|
const { frontmatter } = parseFrontmatter(text);
|
|
1321
1031
|
const marker = resourceType === "skill" ? "SKILL.md" : "PHASE.md";
|
|
1322
1032
|
name = frontmatter.name ?? inferResourceName(resolved.absolutePath, marker);
|
|
1323
|
-
baseDir =
|
|
1033
|
+
baseDir = dirname3(resolved.absolutePath);
|
|
1324
1034
|
} else {
|
|
1325
1035
|
name = inferResourceName(resolved.absolutePath, basename2(resolved.absolutePath));
|
|
1326
1036
|
}
|
|
@@ -1351,7 +1061,7 @@ function createWriteTool(context) {
|
|
|
1351
1061
|
async execute(args, toolContext) {
|
|
1352
1062
|
const parsed = WriteArgsValidator.Parse(args);
|
|
1353
1063
|
const resolved = resolveCoreToolPath(context, parsed.path);
|
|
1354
|
-
await mkdir(
|
|
1064
|
+
await mkdir(dirname3(resolved.absolutePath), { recursive: true });
|
|
1355
1065
|
await writeFile(resolved.absolutePath, parsed.content, "utf8");
|
|
1356
1066
|
return toolResult({
|
|
1357
1067
|
context: toolContext,
|
|
@@ -1388,7 +1098,7 @@ function createEditTool(context) {
|
|
|
1388
1098
|
});
|
|
1389
1099
|
}
|
|
1390
1100
|
const resolved = resolveCoreToolPath(context, parsed.path);
|
|
1391
|
-
const current = await
|
|
1101
|
+
const current = await readFile2(resolved.absolutePath, "utf8");
|
|
1392
1102
|
if (!current.includes(parsed.oldText)) {
|
|
1393
1103
|
return toolResult({
|
|
1394
1104
|
context: toolContext,
|
|
@@ -1576,15 +1286,6 @@ function serializeSkills(skills) {
|
|
|
1576
1286
|
disableModelInvocation: skill.disableModelInvocation
|
|
1577
1287
|
}));
|
|
1578
1288
|
}
|
|
1579
|
-
function latestUserInput(messages) {
|
|
1580
|
-
for (let index = messages.length - 1; index >= 0; index -= 1) {
|
|
1581
|
-
const message = messages[index];
|
|
1582
|
-
if (message.role === "user") {
|
|
1583
|
-
return messageContentText(message.content);
|
|
1584
|
-
}
|
|
1585
|
-
}
|
|
1586
|
-
return "";
|
|
1587
|
-
}
|
|
1588
1289
|
function conversationMessages(messages) {
|
|
1589
1290
|
return messages.flatMap((message) => {
|
|
1590
1291
|
if (message.role === "user") {
|
|
@@ -2398,13 +2099,13 @@ async function completeRun2(config, state, outcome) {
|
|
|
2398
2099
|
}
|
|
2399
2100
|
|
|
2400
2101
|
// src/harness/skills.ts
|
|
2401
|
-
import { existsSync as
|
|
2102
|
+
import { existsSync as existsSync2, statSync as statSync2 } from "fs";
|
|
2402
2103
|
import { readdir as readdir2 } from "fs/promises";
|
|
2403
|
-
import { dirname as
|
|
2104
|
+
import { dirname as dirname4, join as join2, resolve as resolve4 } from "path";
|
|
2404
2105
|
var SKILL_MARKER = "SKILL.md";
|
|
2405
2106
|
function resolveSkillPath(input) {
|
|
2406
2107
|
const resolved = resolve4(input);
|
|
2407
|
-
return
|
|
2108
|
+
return existsSync2(resolved) && statSync2(resolved).isDirectory() ? join2(resolved, SKILL_MARKER) : resolved;
|
|
2408
2109
|
}
|
|
2409
2110
|
function readSkillContent(skill) {
|
|
2410
2111
|
return formatResourceOutput({
|
|
@@ -2422,25 +2123,25 @@ async function loadSkill(path) {
|
|
|
2422
2123
|
name: frontmatter.name ?? inferResourceName(resolved, SKILL_MARKER),
|
|
2423
2124
|
description: frontmatter.description ?? "",
|
|
2424
2125
|
filePath: resolved,
|
|
2425
|
-
baseDir:
|
|
2126
|
+
baseDir: dirname4(resolved),
|
|
2426
2127
|
content: body,
|
|
2427
2128
|
disableModelInvocation: frontmatter["disable-model-invocation"] === true
|
|
2428
2129
|
};
|
|
2429
2130
|
}
|
|
2430
2131
|
async function loadSkills(targetPath) {
|
|
2431
2132
|
const skillsDir = resolve4(targetPath);
|
|
2432
|
-
if (
|
|
2133
|
+
if (existsSync2(skillsDir) && statSync2(skillsDir).isFile()) {
|
|
2433
2134
|
return [await loadSkill(skillsDir)];
|
|
2434
2135
|
}
|
|
2435
|
-
if (
|
|
2136
|
+
if (existsSync2(join2(skillsDir, SKILL_MARKER))) {
|
|
2436
2137
|
return [await loadSkill(skillsDir)];
|
|
2437
2138
|
}
|
|
2438
2139
|
const entries = await readdir2(skillsDir, { withFileTypes: true });
|
|
2439
2140
|
const skills = [];
|
|
2440
2141
|
for (const entry of [...entries].sort((a, b) => a.name.localeCompare(b.name))) {
|
|
2441
2142
|
if (!entry.isDirectory()) continue;
|
|
2442
|
-
const skillFile =
|
|
2443
|
-
if (!
|
|
2143
|
+
const skillFile = join2(skillsDir, entry.name, SKILL_MARKER);
|
|
2144
|
+
if (!existsSync2(skillFile)) continue;
|
|
2444
2145
|
try {
|
|
2445
2146
|
const skill = await loadSkill(skillFile);
|
|
2446
2147
|
skills.push(skill);
|
|
@@ -2561,19 +2262,11 @@ var HooksManager = class {
|
|
|
2561
2262
|
return void 0;
|
|
2562
2263
|
}
|
|
2563
2264
|
};
|
|
2564
|
-
var _globalHooks;
|
|
2565
|
-
function getGlobalHooks() {
|
|
2566
|
-
_globalHooks ??= new HooksManager();
|
|
2567
|
-
return _globalHooks;
|
|
2568
|
-
}
|
|
2569
|
-
function resetGlobalHooks() {
|
|
2570
|
-
_globalHooks = void 0;
|
|
2571
|
-
}
|
|
2572
2265
|
|
|
2573
2266
|
// src/extensions/runner.ts
|
|
2574
2267
|
import { execFile } from "child_process";
|
|
2575
2268
|
import {
|
|
2576
|
-
registerModel
|
|
2269
|
+
registerModel,
|
|
2577
2270
|
unregisterProviderModels,
|
|
2578
2271
|
registerApiProvider
|
|
2579
2272
|
} from "@rowan-agent/models";
|
|
@@ -2624,6 +2317,9 @@ function createExtensionRuntime() {
|
|
|
2624
2317
|
return runtime;
|
|
2625
2318
|
}
|
|
2626
2319
|
|
|
2320
|
+
// src/extensions/runner.ts
|
|
2321
|
+
import { parseModelRef as parseModelRef2 } from "@rowan-agent/models";
|
|
2322
|
+
|
|
2627
2323
|
// src/extensions/event-bus.ts
|
|
2628
2324
|
function createEventBus() {
|
|
2629
2325
|
const listeners = /* @__PURE__ */ new Map();
|
|
@@ -2705,7 +2401,7 @@ function applyProviderRegistration(config) {
|
|
|
2705
2401
|
registerApiProvider({ protocol: config.protocol, stream: config.streamSimple });
|
|
2706
2402
|
}
|
|
2707
2403
|
for (const modelConfig of config.models) {
|
|
2708
|
-
|
|
2404
|
+
registerModel({
|
|
2709
2405
|
id: modelConfig.id,
|
|
2710
2406
|
name: modelConfig.name,
|
|
2711
2407
|
protocol: config.protocol,
|
|
@@ -2977,7 +2673,7 @@ var ExtensionRunner = class {
|
|
|
2977
2673
|
filePath: "",
|
|
2978
2674
|
baseDir: "",
|
|
2979
2675
|
content: "",
|
|
2980
|
-
...def.model ? { model:
|
|
2676
|
+
...def.model ? { model: parseModelRef2(def.model) } : {}
|
|
2981
2677
|
};
|
|
2982
2678
|
}
|
|
2983
2679
|
// ---------------------------------------------------------------------------
|
|
@@ -3300,10 +2996,10 @@ function createExtensionRunner(options) {
|
|
|
3300
2996
|
}
|
|
3301
2997
|
|
|
3302
2998
|
// src/extensions/loader.ts
|
|
3303
|
-
import { existsSync as
|
|
3304
|
-
import { readdir as readdir3, readFile as
|
|
2999
|
+
import { existsSync as existsSync3, readFileSync } from "fs";
|
|
3000
|
+
import { readdir as readdir3, readFile as readFile3, stat as stat2 } from "fs/promises";
|
|
3305
3001
|
import { fileURLToPath } from "url";
|
|
3306
|
-
import { dirname as
|
|
3002
|
+
import { basename as basename3, dirname as dirname5, extname as extname2, join as join3, resolve as resolve5 } from "path";
|
|
3307
3003
|
import { createJiti } from "jiti";
|
|
3308
3004
|
function isExtensionFile(path) {
|
|
3309
3005
|
const extension = extname2(path).toLowerCase();
|
|
@@ -3313,12 +3009,12 @@ function isSyntheticPath(path) {
|
|
|
3313
3009
|
return path.startsWith("<") && path.endsWith(">");
|
|
3314
3010
|
}
|
|
3315
3011
|
function readManifestSync(dir) {
|
|
3316
|
-
const manifestPath =
|
|
3317
|
-
if (!
|
|
3012
|
+
const manifestPath = join3(dir, "package.json");
|
|
3013
|
+
if (!existsSync3(manifestPath)) {
|
|
3318
3014
|
return void 0;
|
|
3319
3015
|
}
|
|
3320
3016
|
try {
|
|
3321
|
-
const content =
|
|
3017
|
+
const content = readFileSync(manifestPath, "utf8");
|
|
3322
3018
|
const pkg = JSON.parse(content);
|
|
3323
3019
|
const rowan = pkg.rowan;
|
|
3324
3020
|
if (!rowan) return void 0;
|
|
@@ -3336,7 +3032,7 @@ function jitiAliases() {
|
|
|
3336
3032
|
return {};
|
|
3337
3033
|
}
|
|
3338
3034
|
const agentSourcePath = fileURLToPath(new URL("../index.ts", moduleUrl));
|
|
3339
|
-
if (!
|
|
3035
|
+
if (!existsSync3(agentSourcePath)) {
|
|
3340
3036
|
return {};
|
|
3341
3037
|
}
|
|
3342
3038
|
return { "@rowan-agent/agent": agentSourcePath };
|
|
@@ -3344,7 +3040,9 @@ function jitiAliases() {
|
|
|
3344
3040
|
var sharedJiti;
|
|
3345
3041
|
function getJiti() {
|
|
3346
3042
|
sharedJiti ??= createJiti(import.meta.url || process.cwd(), {
|
|
3043
|
+
fsCache: false,
|
|
3347
3044
|
moduleCache: false,
|
|
3045
|
+
tryNative: false,
|
|
3348
3046
|
alias: jitiAliases()
|
|
3349
3047
|
});
|
|
3350
3048
|
return sharedJiti;
|
|
@@ -3355,21 +3053,21 @@ async function loadExtensionModule(extensionPath) {
|
|
|
3355
3053
|
return typeof module === "function" ? module : void 0;
|
|
3356
3054
|
}
|
|
3357
3055
|
async function readPackageManifest(path) {
|
|
3358
|
-
if (!
|
|
3056
|
+
if (!existsSync3(path)) {
|
|
3359
3057
|
return void 0;
|
|
3360
3058
|
}
|
|
3361
|
-
const content = await
|
|
3059
|
+
const content = await readFile3(path, "utf8");
|
|
3362
3060
|
return JSON.parse(content);
|
|
3363
3061
|
}
|
|
3364
3062
|
async function resolveExtensionEntries(dir) {
|
|
3365
|
-
const manifest = await readPackageManifest(
|
|
3063
|
+
const manifest = await readPackageManifest(join3(dir, "package.json"));
|
|
3366
3064
|
const declared = manifest?.rowan?.extensions;
|
|
3367
3065
|
if (declared && declared.length > 0) {
|
|
3368
3066
|
return declared.map((entry) => resolve5(dir, entry));
|
|
3369
3067
|
}
|
|
3370
3068
|
for (const name of ["index.ts", "index.js"]) {
|
|
3371
|
-
const entry =
|
|
3372
|
-
if (
|
|
3069
|
+
const entry = join3(dir, name);
|
|
3070
|
+
if (existsSync3(entry)) {
|
|
3373
3071
|
return [entry];
|
|
3374
3072
|
}
|
|
3375
3073
|
}
|
|
@@ -3384,7 +3082,7 @@ async function discoverExtensionsInDir(dir) {
|
|
|
3384
3082
|
});
|
|
3385
3083
|
const paths = [];
|
|
3386
3084
|
for (const entry of [...entries].sort((a, b) => a.name.localeCompare(b.name))) {
|
|
3387
|
-
const entryPath =
|
|
3085
|
+
const entryPath = join3(dir, entry.name);
|
|
3388
3086
|
if ((entry.isFile() || entry.isSymbolicLink()) && isExtensionFile(entry.name)) {
|
|
3389
3087
|
paths.push(entryPath);
|
|
3390
3088
|
continue;
|
|
@@ -3404,8 +3102,8 @@ async function discoverExtensionsInDir(dir) {
|
|
|
3404
3102
|
function loadExtensionFromFactory(factory, cwd, extensionPath = "<inline>") {
|
|
3405
3103
|
const resolvedCwd = resolve5(cwd);
|
|
3406
3104
|
const resolvedPath = isSyntheticPath(extensionPath) ? extensionPath : resolve5(resolvedCwd, extensionPath);
|
|
3407
|
-
const name = isSyntheticPath(extensionPath) ? extensionPath :
|
|
3408
|
-
const manifestDir = isSyntheticPath(extensionPath) ? resolvedCwd :
|
|
3105
|
+
const name = isSyntheticPath(extensionPath) ? extensionPath : basename3(dirname5(resolvedPath)) || "unknown";
|
|
3106
|
+
const manifestDir = isSyntheticPath(extensionPath) ? resolvedCwd : dirname5(resolvedPath);
|
|
3409
3107
|
const manifest = readManifestSync(manifestDir);
|
|
3410
3108
|
return {
|
|
3411
3109
|
path: resolvedPath,
|
|
@@ -3443,14 +3141,14 @@ async function loadExtensionsFromPath(path) {
|
|
|
3443
3141
|
const resolvedPath = resolve5(path);
|
|
3444
3142
|
const info = await stat2(resolvedPath);
|
|
3445
3143
|
if (!info.isDirectory()) {
|
|
3446
|
-
return loadExtensions([resolvedPath],
|
|
3144
|
+
return loadExtensions([resolvedPath], dirname5(resolvedPath));
|
|
3447
3145
|
}
|
|
3448
3146
|
const entries = await resolveExtensionEntries(resolvedPath) ?? await discoverExtensionsInDir(resolvedPath);
|
|
3449
3147
|
return loadExtensions(entries, resolvedPath);
|
|
3450
3148
|
}
|
|
3451
3149
|
|
|
3452
|
-
// src/agent.ts
|
|
3453
|
-
var
|
|
3150
|
+
// src/agent-execution.ts
|
|
3151
|
+
var AgentExecution = class {
|
|
3454
3152
|
state;
|
|
3455
3153
|
options;
|
|
3456
3154
|
listeners = /* @__PURE__ */ new Set();
|
|
@@ -3460,6 +3158,17 @@ var Agent = class {
|
|
|
3460
3158
|
extensionRunner;
|
|
3461
3159
|
loadedExtensions;
|
|
3462
3160
|
loadedExtensionsCwd;
|
|
3161
|
+
runtimeRunId;
|
|
3162
|
+
get id() {
|
|
3163
|
+
const agentId = this.state.agentId;
|
|
3164
|
+
if (!agentId) throw new Error("Agent has no durable identity.");
|
|
3165
|
+
return agentId;
|
|
3166
|
+
}
|
|
3167
|
+
get sessionId() {
|
|
3168
|
+
const sessionId = this.state.sessionId;
|
|
3169
|
+
if (!sessionId) throw new Error("Agent has no Session.");
|
|
3170
|
+
return sessionId;
|
|
3171
|
+
}
|
|
3463
3172
|
static loadSkills(targetPath) {
|
|
3464
3173
|
return loadSkills(targetPath);
|
|
3465
3174
|
}
|
|
@@ -3476,6 +3185,7 @@ var Agent = class {
|
|
|
3476
3185
|
context
|
|
3477
3186
|
};
|
|
3478
3187
|
this.state = {
|
|
3188
|
+
...this.options.agentId ? { agentId: this.options.agentId } : {},
|
|
3479
3189
|
...this.options.sessionId ? { sessionId: this.options.sessionId } : {},
|
|
3480
3190
|
context: prepareAgentContext(context),
|
|
3481
3191
|
model: this.options.model,
|
|
@@ -3511,7 +3221,7 @@ var Agent = class {
|
|
|
3511
3221
|
this.appendUserMessage(input);
|
|
3512
3222
|
return this.run(options);
|
|
3513
3223
|
}
|
|
3514
|
-
runWithMessage(message, options) {
|
|
3224
|
+
runWithMessage(message, options, internalHooks = {}) {
|
|
3515
3225
|
const activeRun = this.activeRun;
|
|
3516
3226
|
if (activeRun?.resume) {
|
|
3517
3227
|
activeRun.resume([message]);
|
|
@@ -3521,7 +3231,17 @@ var Agent = class {
|
|
|
3521
3231
|
return Promise.reject(new Error("Agent is already running."));
|
|
3522
3232
|
}
|
|
3523
3233
|
this.appendMessage(message);
|
|
3524
|
-
return this.run(options);
|
|
3234
|
+
return this.run(options, internalHooks);
|
|
3235
|
+
}
|
|
3236
|
+
/** Internal Runtime seam: executes one leased Agent Input. */
|
|
3237
|
+
async executeAgentInput(input, runId, control) {
|
|
3238
|
+
this.runtimeRunId = runId;
|
|
3239
|
+
const internal = { onSuspend: control.suspend };
|
|
3240
|
+
try {
|
|
3241
|
+
return (await this.runWithMessage(input, void 0, internal)).outcome;
|
|
3242
|
+
} finally {
|
|
3243
|
+
if (!this.activeRun) this.runtimeRunId = void 0;
|
|
3244
|
+
}
|
|
3525
3245
|
}
|
|
3526
3246
|
resetInitialization() {
|
|
3527
3247
|
this.state.initialized = false;
|
|
@@ -3593,6 +3313,13 @@ var Agent = class {
|
|
|
3593
3313
|
getSessionId() {
|
|
3594
3314
|
return this.state.sessionId;
|
|
3595
3315
|
}
|
|
3316
|
+
getAgentId() {
|
|
3317
|
+
return this.state.agentId;
|
|
3318
|
+
}
|
|
3319
|
+
/** Internal Runtime hook for associating Tool Calls with the leased Run. */
|
|
3320
|
+
getRuntimeRunId() {
|
|
3321
|
+
return this.runtimeRunId;
|
|
3322
|
+
}
|
|
3596
3323
|
setModel(model) {
|
|
3597
3324
|
this.options = {
|
|
3598
3325
|
...this.options,
|
|
@@ -3740,18 +3467,25 @@ var Agent = class {
|
|
|
3740
3467
|
const message = error instanceof Error ? error.message : "Agent run failed.";
|
|
3741
3468
|
this.state.error = message;
|
|
3742
3469
|
}
|
|
3743
|
-
runWithLifecycle(executor) {
|
|
3470
|
+
runWithLifecycle(executor, hooks = {}) {
|
|
3744
3471
|
if (this.activeRun) {
|
|
3745
3472
|
return Promise.reject(new Error("Agent is already running."));
|
|
3746
3473
|
}
|
|
3747
3474
|
const abortController = new AbortController();
|
|
3748
|
-
|
|
3749
|
-
|
|
3750
|
-
|
|
3751
|
-
|
|
3752
|
-
|
|
3753
|
-
}
|
|
3754
|
-
|
|
3475
|
+
let suspensionRequested = false;
|
|
3476
|
+
const waitForInput = () => {
|
|
3477
|
+
if (!suspensionRequested) {
|
|
3478
|
+
suspensionRequested = true;
|
|
3479
|
+
void hooks.onSuspend?.("Agent requested input.");
|
|
3480
|
+
}
|
|
3481
|
+
return new Promise((resolve7) => {
|
|
3482
|
+
const activeRun = this.activeRun;
|
|
3483
|
+
activeRun.resume = (messages) => {
|
|
3484
|
+
activeRun.resume = void 0;
|
|
3485
|
+
resolve7(messages);
|
|
3486
|
+
};
|
|
3487
|
+
});
|
|
3488
|
+
};
|
|
3755
3489
|
const promise = Promise.resolve().then(() => executor({ signal: abortController.signal, waitForInput })).catch((error) => {
|
|
3756
3490
|
this.handleRunFailure(error, abortController.signal.aborted);
|
|
3757
3491
|
throw error;
|
|
@@ -3785,7 +3519,7 @@ var Agent = class {
|
|
|
3785
3519
|
this.loadedExtensions = extensions;
|
|
3786
3520
|
this.loadedExtensionsCwd = cwd;
|
|
3787
3521
|
}
|
|
3788
|
-
run(config) {
|
|
3522
|
+
run(config, internalHooks = {}) {
|
|
3789
3523
|
return this.runWithLifecycle(async ({ signal, waitForInput }) => {
|
|
3790
3524
|
const resolved = this.resolveRunConfig(config);
|
|
3791
3525
|
const extensions = resolved.extensions ?? [];
|
|
@@ -3846,7 +3580,8 @@ var Agent = class {
|
|
|
3846
3580
|
onMessage: resolved.onMessage,
|
|
3847
3581
|
onOutcome: resolved.onOutcome,
|
|
3848
3582
|
onModelTranscript: resolved.onModelTranscript,
|
|
3849
|
-
waitForInput
|
|
3583
|
+
waitForInput,
|
|
3584
|
+
runtime: resolved.runtime
|
|
3850
3585
|
});
|
|
3851
3586
|
const nextContext = {
|
|
3852
3587
|
...prepareAgentContext(resolved.context),
|
|
@@ -3862,13 +3597,20 @@ var Agent = class {
|
|
|
3862
3597
|
context: nextContext
|
|
3863
3598
|
};
|
|
3864
3599
|
return result;
|
|
3865
|
-
});
|
|
3600
|
+
}, internalHooks);
|
|
3866
3601
|
}
|
|
3867
3602
|
abort(reason = "Aborted by caller.") {
|
|
3603
|
+
this.abortLocal(reason);
|
|
3604
|
+
}
|
|
3605
|
+
abortLocal(reason) {
|
|
3868
3606
|
const activeRun = this.activeRun;
|
|
3869
3607
|
activeRun?.resume?.([]);
|
|
3870
3608
|
activeRun?.abortController.abort(reason);
|
|
3871
3609
|
}
|
|
3610
|
+
/** Internal Runtime hook; public callers abort a precise AgentRun. */
|
|
3611
|
+
abortFromRuntime(reason = "Agent Runtime aborted the Run.") {
|
|
3612
|
+
this.abortLocal(reason);
|
|
3613
|
+
}
|
|
3872
3614
|
/**
|
|
3873
3615
|
* Get formatted skill content for LLM consumption.
|
|
3874
3616
|
*
|
|
@@ -3962,9 +3704,126 @@ function prepareAgentContext(context, extensionRunner) {
|
|
|
3962
3704
|
};
|
|
3963
3705
|
}
|
|
3964
3706
|
|
|
3707
|
+
// src/agent.ts
|
|
3708
|
+
var AGENT_CONSTRUCTION = /* @__PURE__ */ Symbol("rowan.agent.construction");
|
|
3709
|
+
var ATTACH_AGENT = /* @__PURE__ */ Symbol("rowan.agent.attach");
|
|
3710
|
+
var Agent = class _Agent {
|
|
3711
|
+
constructor(token, id, sessionId, execution, submitInput) {
|
|
3712
|
+
this.id = id;
|
|
3713
|
+
this.sessionId = sessionId;
|
|
3714
|
+
this.execution = execution;
|
|
3715
|
+
this.submitInput = submitInput;
|
|
3716
|
+
if (token !== AGENT_CONSTRUCTION) {
|
|
3717
|
+
throw new Error("Agent lifecycle is owned by AgentRuntime.");
|
|
3718
|
+
}
|
|
3719
|
+
}
|
|
3720
|
+
id;
|
|
3721
|
+
sessionId;
|
|
3722
|
+
execution;
|
|
3723
|
+
submitInput;
|
|
3724
|
+
static [ATTACH_AGENT](id, sessionId, execution, submitInput) {
|
|
3725
|
+
return new _Agent(AGENT_CONSTRUCTION, id, sessionId, execution, submitInput);
|
|
3726
|
+
}
|
|
3727
|
+
static loadSkills(targetPath) {
|
|
3728
|
+
return AgentExecution.loadSkills(targetPath);
|
|
3729
|
+
}
|
|
3730
|
+
static loadPhases(targetPath) {
|
|
3731
|
+
return AgentExecution.loadPhases(targetPath);
|
|
3732
|
+
}
|
|
3733
|
+
static loadExtensions(targetPath) {
|
|
3734
|
+
return AgentExecution.loadExtensions(targetPath);
|
|
3735
|
+
}
|
|
3736
|
+
subscribe(listener) {
|
|
3737
|
+
return this.execution.subscribe(listener);
|
|
3738
|
+
}
|
|
3739
|
+
flushEvents() {
|
|
3740
|
+
return this.execution.flushEvents();
|
|
3741
|
+
}
|
|
3742
|
+
send(input) {
|
|
3743
|
+
const message = typeof input === "string" ? createMessage("user", input) : input;
|
|
3744
|
+
return this.submitInput(message);
|
|
3745
|
+
}
|
|
3746
|
+
};
|
|
3747
|
+
async function attachAgent(input) {
|
|
3748
|
+
const restoredContext = await input.manager.buildAgentContext({
|
|
3749
|
+
tools: input.options.context.tools,
|
|
3750
|
+
skills: input.options.context.skills
|
|
3751
|
+
});
|
|
3752
|
+
const context = {
|
|
3753
|
+
...restoredContext,
|
|
3754
|
+
systemPrompt: input.options.context.systemPrompt || restoredContext.systemPrompt,
|
|
3755
|
+
phases: input.options.context.phases
|
|
3756
|
+
};
|
|
3757
|
+
const persistInput = async (message) => {
|
|
3758
|
+
await input.manager.appendMessage(message);
|
|
3759
|
+
};
|
|
3760
|
+
let execution;
|
|
3761
|
+
execution = new AgentExecution({
|
|
3762
|
+
...input.options,
|
|
3763
|
+
context,
|
|
3764
|
+
agentId: input.agentId,
|
|
3765
|
+
sessionId: input.sessionId,
|
|
3766
|
+
runtime: {
|
|
3767
|
+
tools: async ({ config, toolCall }) => {
|
|
3768
|
+
const tool = config.context.tools.find((candidate) => candidate.name === toolCall.name);
|
|
3769
|
+
const runId = execution.getRuntimeRunId();
|
|
3770
|
+
if (!tool || !runId) {
|
|
3771
|
+
const message = `Tool ${toolCall.name} is not available to this Agent Run.`;
|
|
3772
|
+
return {
|
|
3773
|
+
toolCallId: toolCall.id,
|
|
3774
|
+
toolName: toolCall.name,
|
|
3775
|
+
ok: false,
|
|
3776
|
+
content: message,
|
|
3777
|
+
error: message
|
|
3778
|
+
};
|
|
3779
|
+
}
|
|
3780
|
+
return input.executeTool({
|
|
3781
|
+
agentId: input.agentId,
|
|
3782
|
+
runId,
|
|
3783
|
+
tool,
|
|
3784
|
+
toolCall,
|
|
3785
|
+
context: config.context,
|
|
3786
|
+
beforeToolCall: config.beforeToolCall,
|
|
3787
|
+
afterToolCall: config.afterToolCall,
|
|
3788
|
+
signal: config.signal
|
|
3789
|
+
});
|
|
3790
|
+
}
|
|
3791
|
+
},
|
|
3792
|
+
onMessage: async (message) => {
|
|
3793
|
+
await input.manager.appendMessage(message);
|
|
3794
|
+
await input.options.onMessage?.(message);
|
|
3795
|
+
},
|
|
3796
|
+
onInput: persistInput,
|
|
3797
|
+
onOutcome: async (outcome) => {
|
|
3798
|
+
await input.manager.appendOutcome(outcome);
|
|
3799
|
+
await input.options.onOutcome?.(outcome);
|
|
3800
|
+
},
|
|
3801
|
+
onModelTranscript: async (transcript, meta) => {
|
|
3802
|
+
await input.manager.appendModelTranscript(transcript, meta);
|
|
3803
|
+
await input.options.onModelTranscript?.(transcript, meta);
|
|
3804
|
+
}
|
|
3805
|
+
});
|
|
3806
|
+
const agent = Agent[ATTACH_AGENT](
|
|
3807
|
+
input.agentId,
|
|
3808
|
+
input.sessionId,
|
|
3809
|
+
execution,
|
|
3810
|
+
(message) => input.submit(message, () => persistInput(message))
|
|
3811
|
+
);
|
|
3812
|
+
return {
|
|
3813
|
+
agent,
|
|
3814
|
+
binding: {
|
|
3815
|
+
abort: (reason) => execution.abortFromRuntime(reason),
|
|
3816
|
+
execute: (message, runId, control) => execution.executeAgentInput(message, runId, control)
|
|
3817
|
+
}
|
|
3818
|
+
};
|
|
3819
|
+
}
|
|
3820
|
+
|
|
3821
|
+
// src/harness/session/jsonl.ts
|
|
3822
|
+
import { appendFile, mkdir as mkdir2, readFile as readFile4, readdir as readdir4, stat as stat3, unlink, writeFile as writeFile2 } from "fs/promises";
|
|
3823
|
+
import { join as join4, relative as relative3, resolve as resolve6, sep as sep3 } from "path";
|
|
3824
|
+
|
|
3965
3825
|
// src/harness/session/session.ts
|
|
3966
3826
|
import Type3 from "typebox";
|
|
3967
|
-
var SESSION_SCHEMA_VERSION = "0.4.4";
|
|
3968
3827
|
var AgentMessageSchema = Type3.Object({
|
|
3969
3828
|
id: Type3.String(),
|
|
3970
3829
|
role: Type3.Union([
|
|
@@ -3985,39 +3844,6 @@ var SkillSchema = Type3.Object({
|
|
|
3985
3844
|
content: Type3.String(),
|
|
3986
3845
|
disableModelInvocation: Type3.Boolean()
|
|
3987
3846
|
});
|
|
3988
|
-
function createMessage2(role, content, metadata) {
|
|
3989
|
-
return {
|
|
3990
|
-
id: createId("msg"),
|
|
3991
|
-
role,
|
|
3992
|
-
content,
|
|
3993
|
-
createdAt: createTimestamp(),
|
|
3994
|
-
...metadata ? { metadata } : {}
|
|
3995
|
-
};
|
|
3996
|
-
}
|
|
3997
|
-
function createSession(input) {
|
|
3998
|
-
const createdAt = createTimestamp();
|
|
3999
|
-
const messages = [
|
|
4000
|
-
createMessage2("user", input.input)
|
|
4001
|
-
];
|
|
4002
|
-
return {
|
|
4003
|
-
version: SESSION_SCHEMA_VERSION,
|
|
4004
|
-
id: input.id ?? createId("ses"),
|
|
4005
|
-
...input.parentSessionId ? { parentSessionId: input.parentSessionId } : {},
|
|
4006
|
-
systemPrompt: input.systemPrompt,
|
|
4007
|
-
input: input.input,
|
|
4008
|
-
messages,
|
|
4009
|
-
log: [],
|
|
4010
|
-
skills: input.skills ?? [],
|
|
4011
|
-
createdAt,
|
|
4012
|
-
updatedAt: createdAt,
|
|
4013
|
-
...input.title ? { title: input.title } : {}
|
|
4014
|
-
};
|
|
4015
|
-
}
|
|
4016
|
-
function appendUserTurn(session, input) {
|
|
4017
|
-
session.messages.push(createMessage2("user", input));
|
|
4018
|
-
session.updatedAt = createTimestamp();
|
|
4019
|
-
return session;
|
|
4020
|
-
}
|
|
4021
3847
|
|
|
4022
3848
|
// src/harness/session/session-manager.ts
|
|
4023
3849
|
var SESSION_MANAGER_SCHEMA_VERSION = "0.4.4";
|
|
@@ -4214,8 +4040,6 @@ function summarizeSessionManagerRecords(records) {
|
|
|
4214
4040
|
}
|
|
4215
4041
|
|
|
4216
4042
|
// src/harness/session/jsonl.ts
|
|
4217
|
-
import { appendFile, mkdir as mkdir2, readFile as readFile5, readdir as readdir4, stat as stat3, unlink, writeFile as writeFile2 } from "fs/promises";
|
|
4218
|
-
import { join as join6, relative as relative3, resolve as resolve6, sep as sep3 } from "path";
|
|
4219
4043
|
var SESSION_ID_PATTERN = /^ses_[A-Za-z0-9_-]+$/;
|
|
4220
4044
|
function isPathInside(parent, child) {
|
|
4221
4045
|
const relativePath = relative3(parent, child);
|
|
@@ -4237,7 +4061,7 @@ function parseRecord(line) {
|
|
|
4237
4061
|
return value;
|
|
4238
4062
|
}
|
|
4239
4063
|
async function readRecords(path) {
|
|
4240
|
-
const text = await
|
|
4064
|
+
const text = await readFile4(path, "utf8").catch((error) => {
|
|
4241
4065
|
if (error.code === "ENOENT") {
|
|
4242
4066
|
return void 0;
|
|
4243
4067
|
}
|
|
@@ -4326,7 +4150,7 @@ var LocalJsonlSessionManager = class _LocalJsonlSessionManager {
|
|
|
4326
4150
|
});
|
|
4327
4151
|
const sessions = await Promise.all(
|
|
4328
4152
|
entries.filter((entry) => entry.isFile() && entry.name.endsWith(".jsonl")).map(async (entry) => {
|
|
4329
|
-
const path =
|
|
4153
|
+
const path = join4(sessionsDir, entry.name);
|
|
4330
4154
|
const records = await readRecords(path);
|
|
4331
4155
|
return records ? summarizeSessionManagerRecords(records) : void 0;
|
|
4332
4156
|
})
|
|
@@ -4416,114 +4240,2004 @@ var LocalJsonlSessionManager = class _LocalJsonlSessionManager {
|
|
|
4416
4240
|
}
|
|
4417
4241
|
};
|
|
4418
4242
|
|
|
4419
|
-
// src/
|
|
4420
|
-
var
|
|
4421
|
-
|
|
4422
|
-
|
|
4423
|
-
|
|
4424
|
-
|
|
4425
|
-
|
|
4426
|
-
|
|
4427
|
-
|
|
4428
|
-
|
|
4429
|
-
|
|
4430
|
-
|
|
4431
|
-
|
|
4432
|
-
|
|
4433
|
-
|
|
4243
|
+
// src/harness/session/provider.ts
|
|
4244
|
+
var InMemorySessionProvider = class {
|
|
4245
|
+
sessions = /* @__PURE__ */ new Map();
|
|
4246
|
+
async create(input) {
|
|
4247
|
+
const session = InMemorySessionManager.create(input);
|
|
4248
|
+
this.sessions.set(session.getSessionId(), session);
|
|
4249
|
+
return session;
|
|
4250
|
+
}
|
|
4251
|
+
async open(sessionId) {
|
|
4252
|
+
return this.sessions.get(sessionId);
|
|
4253
|
+
}
|
|
4254
|
+
};
|
|
4255
|
+
var LocalJsonlSessionProvider = class {
|
|
4256
|
+
constructor(sessionsDir) {
|
|
4257
|
+
this.sessionsDir = sessionsDir;
|
|
4258
|
+
}
|
|
4259
|
+
sessionsDir;
|
|
4260
|
+
create(input) {
|
|
4261
|
+
return LocalJsonlSessionManager.create(this.sessionsDir, input);
|
|
4262
|
+
}
|
|
4263
|
+
open(sessionId) {
|
|
4264
|
+
return LocalJsonlSessionManager.open(this.sessionsDir, sessionId);
|
|
4265
|
+
}
|
|
4266
|
+
list() {
|
|
4267
|
+
return LocalJsonlSessionManager.list(this.sessionsDir);
|
|
4268
|
+
}
|
|
4269
|
+
};
|
|
4270
|
+
|
|
4271
|
+
// src/runtime/tool-runtime.ts
|
|
4272
|
+
function failed(toolCall, message) {
|
|
4273
|
+
return {
|
|
4274
|
+
toolCallId: toolCall.id,
|
|
4275
|
+
toolName: toolCall.name,
|
|
4276
|
+
ok: false,
|
|
4277
|
+
content: message,
|
|
4278
|
+
error: message
|
|
4279
|
+
};
|
|
4280
|
+
}
|
|
4281
|
+
function positiveLimit(value, fallback) {
|
|
4282
|
+
if (value === void 0) return fallback;
|
|
4283
|
+
if (!Number.isFinite(value) || value <= 0) {
|
|
4284
|
+
throw new Error("Tool Runtime concurrency limits must be positive finite numbers.");
|
|
4434
4285
|
}
|
|
4435
|
-
|
|
4436
|
-
|
|
4437
|
-
|
|
4438
|
-
|
|
4439
|
-
|
|
4286
|
+
return Math.floor(value);
|
|
4287
|
+
}
|
|
4288
|
+
var ToolRuntime = class {
|
|
4289
|
+
constructor(stateStore, policy = {}, onTransition = () => void 0) {
|
|
4290
|
+
this.stateStore = stateStore;
|
|
4291
|
+
this.onTransition = onTransition;
|
|
4292
|
+
this.maxConcurrent = positiveLimit(policy.maxConcurrent, Number.MAX_SAFE_INTEGER);
|
|
4293
|
+
this.perToolMaxConcurrent = Object.fromEntries(
|
|
4294
|
+
Object.entries(policy.perToolMaxConcurrent ?? {}).map(([name, value]) => [name, positiveLimit(value, 1)])
|
|
4295
|
+
);
|
|
4296
|
+
this.allowedTools = policy.allowedTools ? new Set(policy.allowedTools) : void 0;
|
|
4297
|
+
}
|
|
4298
|
+
stateStore;
|
|
4299
|
+
onTransition;
|
|
4300
|
+
maxConcurrent;
|
|
4301
|
+
perToolMaxConcurrent;
|
|
4302
|
+
running = 0;
|
|
4303
|
+
runningByTool = /* @__PURE__ */ new Map();
|
|
4304
|
+
waiters = [];
|
|
4305
|
+
activeToolCalls = /* @__PURE__ */ new Map();
|
|
4306
|
+
allowedTools;
|
|
4307
|
+
async execute(input) {
|
|
4308
|
+
const call = await this.stateStore.createToolCall({
|
|
4309
|
+
agentId: input.agentId,
|
|
4310
|
+
runId: input.runId,
|
|
4311
|
+
name: input.toolCall.name,
|
|
4312
|
+
args: input.toolCall.args
|
|
4313
|
+
});
|
|
4314
|
+
await this.onTransition();
|
|
4315
|
+
if (!input.tool || this.allowedTools && !this.allowedTools.has(input.tool.name)) {
|
|
4316
|
+
const result = failed(input.toolCall, `Tool ${input.toolCall.name} is not permitted by Runtime policy.`);
|
|
4317
|
+
await this.stateStore.completeToolCall({ toolCallId: call.id, result, state: "failed" });
|
|
4318
|
+
await this.onTransition();
|
|
4319
|
+
return result;
|
|
4440
4320
|
}
|
|
4441
|
-
const
|
|
4442
|
-
if (
|
|
4443
|
-
|
|
4444
|
-
|
|
4445
|
-
this.
|
|
4321
|
+
const before = await input.beforeToolCall?.({ tool: input.tool, args: input.toolCall.args });
|
|
4322
|
+
if (before && !before.allow) {
|
|
4323
|
+
const result = failed(input.toolCall, before.reason);
|
|
4324
|
+
await this.stateStore.completeToolCall({ toolCallId: call.id, result, state: "failed" });
|
|
4325
|
+
await this.onTransition();
|
|
4326
|
+
return result;
|
|
4327
|
+
}
|
|
4328
|
+
let release;
|
|
4329
|
+
const controller = new AbortController();
|
|
4330
|
+
const forwardAbort = () => controller.abort(input.signal?.reason);
|
|
4331
|
+
input.signal?.addEventListener("abort", forwardAbort, { once: true });
|
|
4332
|
+
this.activeToolCalls.set(call.id, { runId: input.runId, controller });
|
|
4333
|
+
try {
|
|
4334
|
+
try {
|
|
4335
|
+
release = await this.acquire(input.tool.name, controller.signal);
|
|
4336
|
+
} catch (error) {
|
|
4337
|
+
const result2 = failed(input.toolCall, error instanceof Error ? error.message : "Tool execution was cancelled.");
|
|
4338
|
+
await this.stateStore.completeToolCall({ toolCallId: call.id, result: result2, state: "failed" });
|
|
4339
|
+
await this.onTransition();
|
|
4340
|
+
return result2;
|
|
4341
|
+
}
|
|
4342
|
+
if (controller.signal.aborted) {
|
|
4343
|
+
const result2 = failed(input.toolCall, "Tool execution was aborted before it started.");
|
|
4344
|
+
await this.stateStore.completeToolCall({ toolCallId: call.id, result: result2, state: "failed" });
|
|
4345
|
+
await this.onTransition();
|
|
4346
|
+
return result2;
|
|
4347
|
+
}
|
|
4348
|
+
await this.stateStore.startToolCall(call.id);
|
|
4349
|
+
await this.onTransition();
|
|
4350
|
+
let result;
|
|
4351
|
+
try {
|
|
4352
|
+
result = await input.tool.execute(input.toolCall.args, {
|
|
4353
|
+
skills: input.context.skills,
|
|
4354
|
+
toolCallId: call.id
|
|
4355
|
+
}, controller.signal);
|
|
4356
|
+
} catch (error) {
|
|
4357
|
+
if (controller.signal.aborted) {
|
|
4358
|
+
await this.stateStore.markToolCallIndeterminate({
|
|
4359
|
+
toolCallId: call.id,
|
|
4360
|
+
reason: error instanceof Error ? error.message : "Tool execution was interrupted."
|
|
4361
|
+
});
|
|
4362
|
+
await this.onTransition();
|
|
4363
|
+
return failed(input.toolCall, "Tool Call became indeterminate after abort.");
|
|
4364
|
+
}
|
|
4365
|
+
result = failed(input.toolCall, error instanceof Error ? error.message : "Tool execution failed.");
|
|
4366
|
+
}
|
|
4367
|
+
if (controller.signal.aborted) {
|
|
4368
|
+
await this.stateStore.markToolCallIndeterminate({ toolCallId: call.id, reason: "Tool execution was interrupted after it may have had an external effect." });
|
|
4369
|
+
await this.onTransition();
|
|
4370
|
+
return failed(input.toolCall, "Tool Call became indeterminate after abort.");
|
|
4371
|
+
}
|
|
4372
|
+
try {
|
|
4373
|
+
result = await input.afterToolCall?.({ tool: input.tool, result }) ?? result;
|
|
4374
|
+
} catch (error) {
|
|
4375
|
+
result = failed(input.toolCall, error instanceof Error ? error.message : "Tool result review failed.");
|
|
4376
|
+
}
|
|
4377
|
+
await this.stateStore.completeToolCall({
|
|
4378
|
+
toolCallId: call.id,
|
|
4379
|
+
result,
|
|
4380
|
+
state: result.ok ? "completed" : "failed"
|
|
4381
|
+
});
|
|
4382
|
+
await this.onTransition();
|
|
4383
|
+
return result;
|
|
4384
|
+
} finally {
|
|
4385
|
+
input.signal?.removeEventListener("abort", forwardAbort);
|
|
4386
|
+
this.activeToolCalls.delete(call.id);
|
|
4387
|
+
release?.();
|
|
4446
4388
|
}
|
|
4447
4389
|
}
|
|
4448
|
-
|
|
4449
|
-
this.
|
|
4450
|
-
|
|
4451
|
-
this.resolveFinalResult(result);
|
|
4390
|
+
abortRun(runId) {
|
|
4391
|
+
for (const active of this.activeToolCalls.values()) {
|
|
4392
|
+
if (active.runId === runId) active.controller.abort("Agent Run aborted.");
|
|
4452
4393
|
}
|
|
4453
|
-
|
|
4454
|
-
|
|
4455
|
-
|
|
4394
|
+
}
|
|
4395
|
+
async acquire(toolName, signal) {
|
|
4396
|
+
if (signal.aborted) throw new Error("Tool execution aborted while waiting for capacity.");
|
|
4397
|
+
const limit = this.perToolMaxConcurrent[toolName] ?? this.maxConcurrent;
|
|
4398
|
+
if (this.running < this.maxConcurrent && (this.runningByTool.get(toolName) ?? 0) < limit) {
|
|
4399
|
+
return this.reserve(toolName);
|
|
4456
4400
|
}
|
|
4401
|
+
return new Promise((resolve7, reject) => {
|
|
4402
|
+
let waiter;
|
|
4403
|
+
const onAbort = () => {
|
|
4404
|
+
const index = this.waiters.indexOf(waiter);
|
|
4405
|
+
if (index >= 0) this.waiters.splice(index, 1);
|
|
4406
|
+
reject(new Error("Tool execution aborted while waiting for capacity."));
|
|
4407
|
+
};
|
|
4408
|
+
waiter = { toolName, signal, onAbort, resolve: resolve7, reject };
|
|
4409
|
+
this.waiters.push(waiter);
|
|
4410
|
+
signal.addEventListener("abort", onAbort, { once: true });
|
|
4411
|
+
});
|
|
4412
|
+
}
|
|
4413
|
+
reserve(toolName) {
|
|
4414
|
+
this.running += 1;
|
|
4415
|
+
this.runningByTool.set(toolName, (this.runningByTool.get(toolName) ?? 0) + 1);
|
|
4416
|
+
let released = false;
|
|
4417
|
+
return () => {
|
|
4418
|
+
if (released) return;
|
|
4419
|
+
released = true;
|
|
4420
|
+
this.running -= 1;
|
|
4421
|
+
const count = (this.runningByTool.get(toolName) ?? 1) - 1;
|
|
4422
|
+
if (count <= 0) this.runningByTool.delete(toolName);
|
|
4423
|
+
else this.runningByTool.set(toolName, count);
|
|
4424
|
+
this.pumpWaiters();
|
|
4425
|
+
};
|
|
4426
|
+
}
|
|
4427
|
+
pumpWaiters() {
|
|
4428
|
+
for (let index = 0; index < this.waiters.length; index += 1) {
|
|
4429
|
+
const waiter = this.waiters[index];
|
|
4430
|
+
const limit = this.perToolMaxConcurrent[waiter.toolName] ?? this.maxConcurrent;
|
|
4431
|
+
if (this.running >= this.maxConcurrent || (this.runningByTool.get(waiter.toolName) ?? 0) >= limit) continue;
|
|
4432
|
+
this.waiters.splice(index, 1);
|
|
4433
|
+
waiter.signal.removeEventListener("abort", waiter.onAbort);
|
|
4434
|
+
waiter.resolve(this.reserve(waiter.toolName));
|
|
4435
|
+
index -= 1;
|
|
4436
|
+
}
|
|
4437
|
+
}
|
|
4438
|
+
};
|
|
4439
|
+
|
|
4440
|
+
// src/runtime/agent-run.ts
|
|
4441
|
+
var AGENT_RUN_CONSTRUCTION = /* @__PURE__ */ Symbol("rowan.agentRun.construction");
|
|
4442
|
+
var CREATE_AGENT_RUN = /* @__PURE__ */ Symbol("rowan.agentRun.create");
|
|
4443
|
+
var AgentRun = class _AgentRun {
|
|
4444
|
+
constructor(token, host, initial, publicMessageId) {
|
|
4445
|
+
this.host = host;
|
|
4446
|
+
this.publicMessageId = publicMessageId;
|
|
4447
|
+
if (token !== AGENT_RUN_CONSTRUCTION) {
|
|
4448
|
+
throw new Error("AgentRun lifecycle is owned by AgentRuntime.");
|
|
4449
|
+
}
|
|
4450
|
+
this.current = structuredClone(initial);
|
|
4451
|
+
host.register(this);
|
|
4452
|
+
}
|
|
4453
|
+
host;
|
|
4454
|
+
publicMessageId;
|
|
4455
|
+
current;
|
|
4456
|
+
listeners = /* @__PURE__ */ new Set();
|
|
4457
|
+
static [CREATE_AGENT_RUN](host, initial, publicMessageId) {
|
|
4458
|
+
return new _AgentRun(AGENT_RUN_CONSTRUCTION, host, initial, publicMessageId);
|
|
4459
|
+
}
|
|
4460
|
+
get id() {
|
|
4461
|
+
return this.current.id;
|
|
4462
|
+
}
|
|
4463
|
+
get messageId() {
|
|
4464
|
+
return this.publicMessageId;
|
|
4465
|
+
}
|
|
4466
|
+
get status() {
|
|
4467
|
+
return this.current.state;
|
|
4468
|
+
}
|
|
4469
|
+
get state() {
|
|
4470
|
+
return this.current.state;
|
|
4471
|
+
}
|
|
4472
|
+
async getStatus() {
|
|
4473
|
+
const run = await this.host.getRun(this.id);
|
|
4474
|
+
if (run) this.notify(run);
|
|
4475
|
+
return this.current.state;
|
|
4457
4476
|
}
|
|
4458
|
-
|
|
4477
|
+
subscribe(listener) {
|
|
4478
|
+
this.listeners.add(listener);
|
|
4479
|
+
return () => this.listeners.delete(listener);
|
|
4480
|
+
}
|
|
4481
|
+
consumeRuntimeEvents(consumerId, listener) {
|
|
4482
|
+
return this.host.consumeEvents(consumerId, (event) => {
|
|
4483
|
+
if (event.runId === this.id) return listener(event);
|
|
4484
|
+
});
|
|
4485
|
+
}
|
|
4486
|
+
async result() {
|
|
4459
4487
|
while (true) {
|
|
4460
|
-
|
|
4461
|
-
|
|
4462
|
-
|
|
4488
|
+
const run = await this.host.getRun(this.id);
|
|
4489
|
+
if (!run) throw new Error(`Agent Run not found: ${this.id}.`);
|
|
4490
|
+
this.notify(run);
|
|
4491
|
+
if (["completed", "failed", "cancelled"].includes(run.state)) {
|
|
4492
|
+
if (!run.outcome) {
|
|
4493
|
+
throw new Error(`Agent Run ${this.id} reached ${run.state} without an Outcome.`);
|
|
4494
|
+
}
|
|
4495
|
+
return structuredClone(run.outcome);
|
|
4496
|
+
}
|
|
4497
|
+
await this.host.waitForRunChange(this.id, run.state, run.updatedAt);
|
|
4498
|
+
}
|
|
4499
|
+
}
|
|
4500
|
+
async abort(reason = "Agent Run aborted by caller.") {
|
|
4501
|
+
await this.host.abortRun(this.id, reason);
|
|
4502
|
+
}
|
|
4503
|
+
notify(run) {
|
|
4504
|
+
this.current = structuredClone(run);
|
|
4505
|
+
for (const listener of this.listeners) {
|
|
4506
|
+
try {
|
|
4507
|
+
const result = listener(run.state);
|
|
4508
|
+
if (result && typeof result.then === "function") {
|
|
4509
|
+
void Promise.resolve(result).catch(() => void 0);
|
|
4510
|
+
}
|
|
4511
|
+
} catch {
|
|
4512
|
+
}
|
|
4513
|
+
}
|
|
4514
|
+
}
|
|
4515
|
+
};
|
|
4516
|
+
function createAgentRun(host, initial, publicMessageId = initial.messageId) {
|
|
4517
|
+
return AgentRun[CREATE_AGENT_RUN](host, initial, publicMessageId);
|
|
4518
|
+
}
|
|
4519
|
+
|
|
4520
|
+
// src/runtime/agent-runtime.ts
|
|
4521
|
+
var InfrastructureFailureError = class extends Error {
|
|
4522
|
+
constructor(message, options) {
|
|
4523
|
+
super(message, options);
|
|
4524
|
+
this.name = "InfrastructureFailureError";
|
|
4525
|
+
}
|
|
4526
|
+
};
|
|
4527
|
+
var activeRuntime;
|
|
4528
|
+
var AgentRuntime = class _AgentRuntime {
|
|
4529
|
+
stateStore;
|
|
4530
|
+
sessionProvider;
|
|
4531
|
+
maxConcurrentRuns;
|
|
4532
|
+
maxInfrastructureAttempts;
|
|
4533
|
+
leaseDurationMs;
|
|
4534
|
+
leaseRenewalIntervalMs;
|
|
4535
|
+
toolRuntime;
|
|
4536
|
+
factories;
|
|
4537
|
+
bindings = /* @__PURE__ */ new Map();
|
|
4538
|
+
runHandles = /* @__PURE__ */ new Map();
|
|
4539
|
+
runWaiters = /* @__PURE__ */ new Map();
|
|
4540
|
+
pendingRuns = [];
|
|
4541
|
+
scheduledRunIds = /* @__PURE__ */ new Set();
|
|
4542
|
+
runningAgents = /* @__PURE__ */ new Set();
|
|
4543
|
+
pausedAgents = /* @__PURE__ */ new Set();
|
|
4544
|
+
eventSubscriptions = /* @__PURE__ */ new Map();
|
|
4545
|
+
activeDispatches = /* @__PURE__ */ new Set();
|
|
4546
|
+
runningRuns = 0;
|
|
4547
|
+
pumping = false;
|
|
4548
|
+
nextWorkerId = 0;
|
|
4549
|
+
stopped = false;
|
|
4550
|
+
constructor(options) {
|
|
4551
|
+
this.stateStore = options.stateStore;
|
|
4552
|
+
this.sessionProvider = options.sessionProvider;
|
|
4553
|
+
this.maxConcurrentRuns = options.maxConcurrentRuns ?? Number.POSITIVE_INFINITY;
|
|
4554
|
+
this.maxInfrastructureAttempts = options.maxInfrastructureAttempts ?? 3;
|
|
4555
|
+
this.leaseDurationMs = options.leaseDurationMs ?? 6e4;
|
|
4556
|
+
this.leaseRenewalIntervalMs = options.leaseRenewalIntervalMs ?? Math.max(1, Math.floor(this.leaseDurationMs / 2));
|
|
4557
|
+
this.toolRuntime = new ToolRuntime(options.stateStore, options.toolPolicy, () => this.publishEvents());
|
|
4558
|
+
this.factories = options.factories instanceof Map ? new Map([...options.factories.entries()].map(([id, factory]) => [String(id), factory])) : new Map(Object.entries(options.factories ?? {}));
|
|
4559
|
+
for (const id of this.factories.keys()) {
|
|
4560
|
+
if (id.trim().length === 0) throw new Error("Factory ID must not be empty.");
|
|
4561
|
+
}
|
|
4562
|
+
}
|
|
4563
|
+
static async start(options) {
|
|
4564
|
+
if (activeRuntime) {
|
|
4565
|
+
throw new Error("Agent Runtime is already started in this process.");
|
|
4566
|
+
}
|
|
4567
|
+
if (!options.stateStore) {
|
|
4568
|
+
throw new Error("Agent Runtime requires a RuntimeStateStore.");
|
|
4569
|
+
}
|
|
4570
|
+
if (!Number.isFinite(options.maxConcurrentRuns ?? 1) || (options.maxConcurrentRuns ?? 1) <= 0) {
|
|
4571
|
+
throw new Error("Agent Runtime maxConcurrentRuns must be a positive finite number.");
|
|
4572
|
+
}
|
|
4573
|
+
if (!Number.isInteger(options.maxInfrastructureAttempts ?? 3) || (options.maxInfrastructureAttempts ?? 3) <= 0) {
|
|
4574
|
+
throw new Error("Agent Runtime maxInfrastructureAttempts must be a positive integer.");
|
|
4575
|
+
}
|
|
4576
|
+
if (!Number.isFinite(options.leaseDurationMs ?? 6e4) || (options.leaseDurationMs ?? 6e4) <= 0) {
|
|
4577
|
+
throw new Error("Agent Runtime leaseDurationMs must be a positive finite number.");
|
|
4578
|
+
}
|
|
4579
|
+
const leaseDurationMs = options.leaseDurationMs ?? 6e4;
|
|
4580
|
+
const renewalIntervalMs = options.leaseRenewalIntervalMs ?? Math.max(1, Math.floor(leaseDurationMs / 2));
|
|
4581
|
+
if (!Number.isFinite(renewalIntervalMs) || renewalIntervalMs <= 0 || renewalIntervalMs >= leaseDurationMs) {
|
|
4582
|
+
throw new Error("Agent Runtime leaseRenewalIntervalMs must be positive and shorter than leaseDurationMs.");
|
|
4583
|
+
}
|
|
4584
|
+
const runtime = new _AgentRuntime(options);
|
|
4585
|
+
activeRuntime = runtime;
|
|
4586
|
+
try {
|
|
4587
|
+
await runtime.recover();
|
|
4588
|
+
return runtime;
|
|
4589
|
+
} catch (error) {
|
|
4590
|
+
await runtime.stop().catch(() => void 0);
|
|
4591
|
+
throw error;
|
|
4592
|
+
}
|
|
4593
|
+
}
|
|
4594
|
+
async createAgent(options) {
|
|
4595
|
+
this.assertRunning();
|
|
4596
|
+
if (options.factoryId !== void 0 && options.factoryId.trim().length === 0) {
|
|
4597
|
+
throw new Error("Factory ID must not be empty.");
|
|
4598
|
+
}
|
|
4599
|
+
const provider = this.sessionProvider;
|
|
4600
|
+
if (!provider) throw new Error("Agent Runtime requires a SessionManager provider to create an Agent.");
|
|
4601
|
+
const manager = await provider.create({
|
|
4602
|
+
systemPrompt: options.context.systemPrompt,
|
|
4603
|
+
input: options.input ?? "",
|
|
4604
|
+
skills: options.context.skills
|
|
4605
|
+
});
|
|
4606
|
+
if (options.input) await manager.appendMessage(createMessage("user", options.input));
|
|
4607
|
+
const record = await this.stateStore.createAgent({
|
|
4608
|
+
sessionId: manager.getSessionId(),
|
|
4609
|
+
...options.factoryId ? { factoryId: options.factoryId } : {}
|
|
4610
|
+
});
|
|
4611
|
+
this.pausedAgents.delete(record.id);
|
|
4612
|
+
const agent = await this.attachAgent(record, manager, options);
|
|
4613
|
+
this.publishEvents();
|
|
4614
|
+
return agent;
|
|
4615
|
+
}
|
|
4616
|
+
async reconstructAgent(agentId, options) {
|
|
4617
|
+
this.assertRunning();
|
|
4618
|
+
const record = await this.stateStore.getAgent(agentId);
|
|
4619
|
+
if (!record) throw new Error(`Agent not found: ${agentId}.`);
|
|
4620
|
+
if (record.state === "paused") this.pausedAgents.add(record.id);
|
|
4621
|
+
else this.pausedAgents.delete(record.id);
|
|
4622
|
+
const provider = this.sessionProvider;
|
|
4623
|
+
if (!provider) throw new Error("Agent Runtime requires a SessionManager provider to reconstruct an Agent.");
|
|
4624
|
+
const manager = await provider.open(record.sessionId);
|
|
4625
|
+
if (!manager) throw new Error(`Session not found: ${record.sessionId}.`);
|
|
4626
|
+
const agent = await this.attachAgent(record, manager, options);
|
|
4627
|
+
this.publishEvents();
|
|
4628
|
+
return agent;
|
|
4629
|
+
}
|
|
4630
|
+
async stop() {
|
|
4631
|
+
if (this.stopped) return;
|
|
4632
|
+
this.stopped = true;
|
|
4633
|
+
for (const binding of this.bindings.values()) {
|
|
4634
|
+
binding.abort("Agent Runtime stopped.");
|
|
4635
|
+
}
|
|
4636
|
+
this.bindings.clear();
|
|
4637
|
+
this.pendingRuns.length = 0;
|
|
4638
|
+
this.scheduledRunIds.clear();
|
|
4639
|
+
this.pausedAgents.clear();
|
|
4640
|
+
const activeDispatches = [...this.activeDispatches];
|
|
4641
|
+
this.runHandles.clear();
|
|
4642
|
+
for (const waiters of this.runWaiters.values()) {
|
|
4643
|
+
for (const resolve7 of waiters) resolve7();
|
|
4644
|
+
}
|
|
4645
|
+
this.runWaiters.clear();
|
|
4646
|
+
this.eventSubscriptions.clear();
|
|
4647
|
+
await Promise.allSettled(activeDispatches);
|
|
4648
|
+
if (activeRuntime === this) activeRuntime = void 0;
|
|
4649
|
+
}
|
|
4650
|
+
bindAgent(agentId, agent) {
|
|
4651
|
+
this.assertRunning();
|
|
4652
|
+
if (this.bindings.has(agentId)) {
|
|
4653
|
+
throw new Error(`Agent ${agentId} is already bound to a live Agent.`);
|
|
4654
|
+
}
|
|
4655
|
+
this.bindings.set(agentId, agent);
|
|
4656
|
+
if (!this.pausedAgents.has(agentId)) void this.scheduleQueuedRuns(agentId);
|
|
4657
|
+
}
|
|
4658
|
+
registerRunHandle(runId, handle) {
|
|
4659
|
+
this.runHandles.set(runId, handle);
|
|
4660
|
+
}
|
|
4661
|
+
waitForRunChange(runId, state, updatedAt) {
|
|
4662
|
+
return new Promise((resolve7) => {
|
|
4663
|
+
const waiters = this.runWaiters.get(runId) ?? /* @__PURE__ */ new Set();
|
|
4664
|
+
let settled = false;
|
|
4665
|
+
const finish = () => {
|
|
4666
|
+
if (settled) return;
|
|
4667
|
+
settled = true;
|
|
4668
|
+
waiters.delete(finish);
|
|
4669
|
+
if (waiters.size === 0) this.runWaiters.delete(runId);
|
|
4670
|
+
resolve7();
|
|
4671
|
+
};
|
|
4672
|
+
waiters.add(finish);
|
|
4673
|
+
this.runWaiters.set(runId, waiters);
|
|
4674
|
+
void this.stateStore.getRun(runId).then((current) => {
|
|
4675
|
+
if (!current || current.state !== state || current.updatedAt !== updatedAt) finish();
|
|
4676
|
+
}, finish);
|
|
4677
|
+
});
|
|
4678
|
+
}
|
|
4679
|
+
scheduleRun(agentId, runId) {
|
|
4680
|
+
this.assertRunning();
|
|
4681
|
+
if (this.scheduledRunIds.has(runId)) return;
|
|
4682
|
+
this.scheduledRunIds.add(runId);
|
|
4683
|
+
this.pendingRuns.push({ agentId, runId });
|
|
4684
|
+
void this.pump();
|
|
4685
|
+
}
|
|
4686
|
+
async pauseAgent(agentId) {
|
|
4687
|
+
this.assertRunning();
|
|
4688
|
+
this.pausedAgents.add(agentId);
|
|
4689
|
+
try {
|
|
4690
|
+
await this.stateStore.setAgentState(agentId, "paused");
|
|
4691
|
+
} catch (error) {
|
|
4692
|
+
this.pausedAgents.delete(agentId);
|
|
4693
|
+
throw error;
|
|
4694
|
+
}
|
|
4695
|
+
this.publishEvents();
|
|
4696
|
+
}
|
|
4697
|
+
async resumeAgent(agentId) {
|
|
4698
|
+
this.assertRunning();
|
|
4699
|
+
await this.stateStore.setAgentState(agentId, "active");
|
|
4700
|
+
this.pausedAgents.delete(agentId);
|
|
4701
|
+
await this.scheduleQueuedRuns(agentId);
|
|
4702
|
+
void this.pump();
|
|
4703
|
+
this.publishEvents();
|
|
4704
|
+
}
|
|
4705
|
+
async abortRun(runId, reason = "Agent Run aborted by caller.") {
|
|
4706
|
+
const run = await this.stateStore.getRun(runId);
|
|
4707
|
+
if (!run || ["completed", "failed", "cancelled"].includes(run.state)) return;
|
|
4708
|
+
const aborted = await this.stateStore.abortRun({
|
|
4709
|
+
runId,
|
|
4710
|
+
outcome: { id: createId("out"), message: reason }
|
|
4711
|
+
});
|
|
4712
|
+
this.notifyRun(aborted);
|
|
4713
|
+
if (run.state === "queued") {
|
|
4714
|
+
const pendingIndex = this.pendingRuns.findIndex((job) => job.runId === runId);
|
|
4715
|
+
if (pendingIndex >= 0) this.pendingRuns.splice(pendingIndex, 1);
|
|
4716
|
+
this.scheduledRunIds.delete(runId);
|
|
4717
|
+
}
|
|
4718
|
+
this.toolRuntime.abortRun(runId);
|
|
4719
|
+
if (run.state === "running" || run.state === "suspended") {
|
|
4720
|
+
this.bindings.get(aborted.agentId)?.abort(reason);
|
|
4721
|
+
}
|
|
4722
|
+
this.publishEvents();
|
|
4723
|
+
}
|
|
4724
|
+
consumeEvents(consumerId, listener) {
|
|
4725
|
+
this.assertRunning();
|
|
4726
|
+
if (consumerId.trim().length === 0) {
|
|
4727
|
+
throw new Error("Runtime Event Consumer ID must not be empty.");
|
|
4728
|
+
}
|
|
4729
|
+
if (this.eventSubscriptions.has(consumerId)) {
|
|
4730
|
+
throw new Error(`Runtime Event Consumer is already active: ${consumerId}.`);
|
|
4731
|
+
}
|
|
4732
|
+
const subscription = {
|
|
4733
|
+
consumerId,
|
|
4734
|
+
listener,
|
|
4735
|
+
delivering: false,
|
|
4736
|
+
redeliver: false
|
|
4737
|
+
};
|
|
4738
|
+
this.eventSubscriptions.set(consumerId, subscription);
|
|
4739
|
+
void this.deliverEvents(subscription).catch(() => void 0);
|
|
4740
|
+
return () => {
|
|
4741
|
+
if (this.eventSubscriptions.get(consumerId) === subscription) {
|
|
4742
|
+
this.eventSubscriptions.delete(consumerId);
|
|
4743
|
+
}
|
|
4744
|
+
};
|
|
4745
|
+
}
|
|
4746
|
+
async listEvents(cursor) {
|
|
4747
|
+
return this.stateStore.listEvents(cursor);
|
|
4748
|
+
}
|
|
4749
|
+
async recover() {
|
|
4750
|
+
await this.stateStore.recoverLeases();
|
|
4751
|
+
const agents = await this.stateStore.listAgents();
|
|
4752
|
+
for (const record of agents) {
|
|
4753
|
+
if (record.state === "paused") this.pausedAgents.add(record.id);
|
|
4754
|
+
}
|
|
4755
|
+
for (const record of agents) {
|
|
4756
|
+
if (record.state !== "active" || !record.factoryId) continue;
|
|
4757
|
+
const factory = this.factories.get(String(record.factoryId));
|
|
4758
|
+
if (!factory) {
|
|
4759
|
+
await this.stateStore.recordEvent({ kind: "factory_missing", related: { agentId: record.id }, payload: { factoryId: record.factoryId } });
|
|
4760
|
+
continue;
|
|
4761
|
+
}
|
|
4762
|
+
try {
|
|
4763
|
+
const options = await factory({ agentId: record.id, sessionId: record.sessionId, factoryId: record.factoryId });
|
|
4764
|
+
if (!options) {
|
|
4765
|
+
await this.stateStore.recordEvent({
|
|
4766
|
+
kind: "agent_recovery_failed",
|
|
4767
|
+
related: { agentId: record.id },
|
|
4768
|
+
payload: { factoryId: record.factoryId, error: "Factory returned no reconstruction options." }
|
|
4769
|
+
});
|
|
4770
|
+
continue;
|
|
4771
|
+
}
|
|
4772
|
+
await this.reconstructAgent(record.id, options);
|
|
4773
|
+
await this.stateStore.recordEvent({ kind: "agent_recovered", related: { agentId: record.id }, payload: { factoryId: record.factoryId } });
|
|
4774
|
+
} catch (error) {
|
|
4775
|
+
await this.stateStore.recordEvent({ kind: "agent_recovery_failed", related: { agentId: record.id }, payload: { factoryId: record.factoryId, error: error instanceof Error ? error.message : String(error) } });
|
|
4776
|
+
}
|
|
4777
|
+
}
|
|
4778
|
+
for (const record of await this.stateStore.listAgents()) {
|
|
4779
|
+
if (this.bindings.has(record.id)) await this.scheduleQueuedRuns(record.id);
|
|
4780
|
+
}
|
|
4781
|
+
this.publishEvents();
|
|
4782
|
+
}
|
|
4783
|
+
async scheduleQueuedRuns(agentId) {
|
|
4784
|
+
if (!this.bindings.has(agentId)) return;
|
|
4785
|
+
const runs = await this.stateStore.listRuns({ agentId, states: ["queued"] });
|
|
4786
|
+
for (const run of runs) this.scheduleRun(agentId, run.id);
|
|
4787
|
+
}
|
|
4788
|
+
async attachAgent(record, manager, options) {
|
|
4789
|
+
const attached = await attachAgent({
|
|
4790
|
+
options,
|
|
4791
|
+
agentId: record.id,
|
|
4792
|
+
sessionId: record.sessionId,
|
|
4793
|
+
manager,
|
|
4794
|
+
submit: (message, persist) => this.submitAgentInput(record.id, message, persist),
|
|
4795
|
+
executeTool: (input) => this.toolRuntime.execute(input)
|
|
4796
|
+
});
|
|
4797
|
+
this.bindAgent(record.id, attached.binding);
|
|
4798
|
+
return attached.agent;
|
|
4799
|
+
}
|
|
4800
|
+
async submitAgentInput(agentId, input, persist) {
|
|
4801
|
+
this.assertRunning();
|
|
4802
|
+
if (!this.bindings.has(agentId)) throw new Error(`Agent ${agentId} has no live Agent Binding.`);
|
|
4803
|
+
const enqueued = await this.stateStore.enqueueAgentInput({ agentId, input });
|
|
4804
|
+
await persist();
|
|
4805
|
+
const run = createAgentRun({
|
|
4806
|
+
register: (handle) => this.registerRunHandle(enqueued.run.id, handle),
|
|
4807
|
+
getRun: (runId) => this.stateStore.getRun(runId),
|
|
4808
|
+
waitForRunChange: (runId, state, updatedAt) => this.waitForRunChange(runId, state, updatedAt),
|
|
4809
|
+
abortRun: (runId, reason) => this.abortRun(runId, reason),
|
|
4810
|
+
consumeEvents: (consumerId, listener) => this.consumeEvents(consumerId, listener)
|
|
4811
|
+
}, enqueued.run, input.id);
|
|
4812
|
+
this.scheduleRun(agentId, enqueued.run.id);
|
|
4813
|
+
this.publishEvents();
|
|
4814
|
+
return run;
|
|
4815
|
+
}
|
|
4816
|
+
async pump() {
|
|
4817
|
+
if (this.pumping || this.stopped) return;
|
|
4818
|
+
this.pumping = true;
|
|
4819
|
+
try {
|
|
4820
|
+
while (this.runningRuns < this.maxConcurrentRuns) {
|
|
4821
|
+
const index = this.pendingRuns.findIndex((job2) => this.bindings.has(job2.agentId) && !this.pausedAgents.has(job2.agentId) && !this.runningAgents.has(job2.agentId));
|
|
4822
|
+
if (index < 0) return;
|
|
4823
|
+
const [job] = this.pendingRuns.splice(index, 1);
|
|
4824
|
+
if (!job) return;
|
|
4825
|
+
this.runningRuns += 1;
|
|
4826
|
+
this.runningAgents.add(job.agentId);
|
|
4827
|
+
let dispatch;
|
|
4828
|
+
dispatch = this.dispatchRun(job).finally(() => {
|
|
4829
|
+
this.runningRuns -= 1;
|
|
4830
|
+
this.runningAgents.delete(job.agentId);
|
|
4831
|
+
this.scheduledRunIds.delete(job.runId);
|
|
4832
|
+
this.activeDispatches.delete(dispatch);
|
|
4833
|
+
void this.pump();
|
|
4834
|
+
});
|
|
4835
|
+
this.activeDispatches.add(dispatch);
|
|
4836
|
+
void dispatch;
|
|
4837
|
+
}
|
|
4838
|
+
} finally {
|
|
4839
|
+
this.pumping = false;
|
|
4840
|
+
}
|
|
4841
|
+
}
|
|
4842
|
+
async dispatchRun(job) {
|
|
4843
|
+
const binding = this.bindings.get(job.agentId);
|
|
4844
|
+
if (!binding) return;
|
|
4845
|
+
const workerId = `runtime-worker-${++this.nextWorkerId}`;
|
|
4846
|
+
let suspendedResolve;
|
|
4847
|
+
let suspended = false;
|
|
4848
|
+
const suspendedSignal = new Promise((resolve7) => {
|
|
4849
|
+
suspendedResolve = resolve7;
|
|
4850
|
+
});
|
|
4851
|
+
const control = {
|
|
4852
|
+
suspend: async (reason) => {
|
|
4853
|
+
if (suspended) return;
|
|
4854
|
+
suspended = true;
|
|
4855
|
+
const current = await this.stateStore.suspendRun({ runId: job.runId, reason });
|
|
4856
|
+
this.notifyRun(current);
|
|
4857
|
+
this.publishEvents();
|
|
4858
|
+
suspendedResolve?.();
|
|
4859
|
+
}
|
|
4860
|
+
};
|
|
4861
|
+
let renewalTimer;
|
|
4862
|
+
let renewalFailed = false;
|
|
4863
|
+
let failRenewal;
|
|
4864
|
+
const renewalFailureSignal = new Promise((resolve7) => {
|
|
4865
|
+
failRenewal = resolve7;
|
|
4866
|
+
});
|
|
4867
|
+
try {
|
|
4868
|
+
const leased = await this.stateStore.leaseRun({
|
|
4869
|
+
runId: job.runId,
|
|
4870
|
+
workerId,
|
|
4871
|
+
leaseDurationMs: this.leaseDurationMs
|
|
4872
|
+
});
|
|
4873
|
+
this.notifyRun(leased.run);
|
|
4874
|
+
renewalTimer = setInterval(() => {
|
|
4875
|
+
void this.stateStore.renewLease({
|
|
4876
|
+
runId: job.runId,
|
|
4877
|
+
leaseId: leased.lease.id,
|
|
4878
|
+
leaseDurationMs: this.leaseDurationMs
|
|
4879
|
+
}).catch((error) => {
|
|
4880
|
+
if (renewalFailed) return;
|
|
4881
|
+
renewalFailed = true;
|
|
4882
|
+
const failure = new InfrastructureFailureError("Agent Run Lease renewal failed.", { cause: error });
|
|
4883
|
+
binding.abort(failure.message);
|
|
4884
|
+
failRenewal(failure);
|
|
4885
|
+
});
|
|
4886
|
+
}, this.leaseRenewalIntervalMs);
|
|
4887
|
+
const execution = binding.execute(leased.message.input, job.runId, control);
|
|
4888
|
+
const outcome = await Promise.race([
|
|
4889
|
+
execution.then((value) => ({ kind: "terminal", value })).catch((error) => ({ kind: "error", error })),
|
|
4890
|
+
suspendedSignal.then(() => ({ kind: "suspended" })),
|
|
4891
|
+
renewalFailureSignal.then((error) => ({ kind: "renewal_failure", error }))
|
|
4892
|
+
]);
|
|
4893
|
+
if (outcome.kind === "suspended") {
|
|
4894
|
+
void execution.catch(() => void 0);
|
|
4895
|
+
return;
|
|
4896
|
+
}
|
|
4897
|
+
if (outcome.kind === "error") {
|
|
4898
|
+
await this.finishFailedRun(job, outcome.error);
|
|
4463
4899
|
return;
|
|
4464
|
-
} else {
|
|
4465
|
-
const result = await new Promise((resolve7) => this.waiting.push(resolve7));
|
|
4466
|
-
if (result.done) return;
|
|
4467
|
-
yield result.value;
|
|
4468
4900
|
}
|
|
4901
|
+
if (outcome.kind === "renewal_failure") {
|
|
4902
|
+
await execution.catch(() => void 0);
|
|
4903
|
+
await this.finishFailedRun(job, outcome.error);
|
|
4904
|
+
return;
|
|
4905
|
+
}
|
|
4906
|
+
await this.finishRun(job, outcome.value);
|
|
4907
|
+
} catch (error) {
|
|
4908
|
+
const current = await this.stateStore.getRun(job.runId);
|
|
4909
|
+
if (!current || ["completed", "failed", "cancelled", "suspended"].includes(current.state)) {
|
|
4910
|
+
if (current) this.notifyRun(current);
|
|
4911
|
+
return;
|
|
4912
|
+
}
|
|
4913
|
+
if (current.state !== "running") {
|
|
4914
|
+
this.notifyRun(current);
|
|
4915
|
+
return;
|
|
4916
|
+
}
|
|
4917
|
+
await this.finishFailedRun(job, error);
|
|
4918
|
+
} finally {
|
|
4919
|
+
if (renewalTimer) clearInterval(renewalTimer);
|
|
4469
4920
|
}
|
|
4470
4921
|
}
|
|
4471
|
-
|
|
4472
|
-
|
|
4922
|
+
async finishRun(job, outcome) {
|
|
4923
|
+
const current = await this.stateStore.getRun(job.runId);
|
|
4924
|
+
if (!current || ["completed", "failed", "cancelled"].includes(current.state)) {
|
|
4925
|
+
if (current) this.notifyRun(current);
|
|
4926
|
+
return;
|
|
4927
|
+
}
|
|
4928
|
+
const completed = await this.stateStore.completeRun({ runId: current.id, outcome });
|
|
4929
|
+
this.notifyRun(completed);
|
|
4930
|
+
this.publishEvents();
|
|
4931
|
+
}
|
|
4932
|
+
async finishFailedRun(job, error) {
|
|
4933
|
+
const current = await this.stateStore.getRun(job.runId);
|
|
4934
|
+
if (!current || ["completed", "failed", "cancelled"].includes(current.state)) {
|
|
4935
|
+
if (current) this.notifyRun(current);
|
|
4936
|
+
return;
|
|
4937
|
+
}
|
|
4938
|
+
const message = error instanceof Error ? error.message : "Agent Run failed.";
|
|
4939
|
+
const outcome = { id: createId("out"), message };
|
|
4940
|
+
const retryable = isRetryableInfrastructureError(error);
|
|
4941
|
+
if (retryable && current.attempt < this.maxInfrastructureAttempts) {
|
|
4942
|
+
const retried = await this.stateStore.retryRun({ runId: current.id, reason: message });
|
|
4943
|
+
this.notifyRun(retried);
|
|
4944
|
+
this.pendingRuns.push(job);
|
|
4945
|
+
this.publishEvents();
|
|
4946
|
+
return;
|
|
4947
|
+
}
|
|
4948
|
+
const failed2 = retryable ? await this.stateStore.exhaustRun({ runId: current.id, outcome, reason: message }) : await this.stateStore.completeRun({ runId: current.id, state: "failed", outcome });
|
|
4949
|
+
this.notifyRun(failed2);
|
|
4950
|
+
this.publishEvents();
|
|
4951
|
+
}
|
|
4952
|
+
notifyRun(run) {
|
|
4953
|
+
this.runHandles.get(run.id)?.notify(run);
|
|
4954
|
+
const waiters = this.runWaiters.get(run.id);
|
|
4955
|
+
if (!waiters) return;
|
|
4956
|
+
this.runWaiters.delete(run.id);
|
|
4957
|
+
for (const resolve7 of waiters) resolve7();
|
|
4958
|
+
}
|
|
4959
|
+
publishEvents() {
|
|
4960
|
+
for (const subscription of this.eventSubscriptions.values()) {
|
|
4961
|
+
void this.deliverEvents(subscription).catch(() => void 0);
|
|
4962
|
+
}
|
|
4963
|
+
}
|
|
4964
|
+
async deliverEvents(subscription) {
|
|
4965
|
+
if (subscription.delivering) {
|
|
4966
|
+
subscription.redeliver = true;
|
|
4967
|
+
return;
|
|
4968
|
+
}
|
|
4969
|
+
subscription.delivering = true;
|
|
4970
|
+
try {
|
|
4971
|
+
do {
|
|
4972
|
+
subscription.redeliver = false;
|
|
4973
|
+
const checkpoint = await this.stateStore.getEventCheckpoint(subscription.consumerId);
|
|
4974
|
+
const events = await this.stateStore.listEvents(
|
|
4975
|
+
checkpoint.eventId ? { after: checkpoint.eventId } : void 0
|
|
4976
|
+
);
|
|
4977
|
+
for (const event of events) {
|
|
4978
|
+
if (this.eventSubscriptions.get(subscription.consumerId) !== subscription) return;
|
|
4979
|
+
await subscription.listener(structuredClone(event));
|
|
4980
|
+
await this.stateStore.acknowledgeEvent(subscription.consumerId, event.id);
|
|
4981
|
+
}
|
|
4982
|
+
} while (subscription.redeliver);
|
|
4983
|
+
} finally {
|
|
4984
|
+
subscription.delivering = false;
|
|
4985
|
+
}
|
|
4986
|
+
}
|
|
4987
|
+
assertRunning() {
|
|
4988
|
+
if (this.stopped || activeRuntime !== this) throw new Error("Agent Runtime is stopped.");
|
|
4473
4989
|
}
|
|
4474
4990
|
};
|
|
4475
|
-
|
|
4476
|
-
|
|
4477
|
-
|
|
4478
|
-
|
|
4479
|
-
|
|
4991
|
+
function isRetryableInfrastructureError(error) {
|
|
4992
|
+
if (error instanceof InfrastructureFailureError) return true;
|
|
4993
|
+
return typeof error === "object" && error !== null && "retryable" in error && error.retryable === true;
|
|
4994
|
+
}
|
|
4995
|
+
|
|
4996
|
+
// src/runtime/memory-store.ts
|
|
4997
|
+
function clone2(value) {
|
|
4998
|
+
return structuredClone(value);
|
|
4999
|
+
}
|
|
5000
|
+
function transitionError(entity, id, state, action) {
|
|
5001
|
+
return new Error(`Cannot ${action} ${entity} ${id} from state "${state}".`);
|
|
5002
|
+
}
|
|
5003
|
+
function requireTransition(entity, id, state, action, allowedStates) {
|
|
5004
|
+
if (!allowedStates.includes(state)) {
|
|
5005
|
+
throw transitionError(entity, id, state, action);
|
|
5006
|
+
}
|
|
5007
|
+
}
|
|
5008
|
+
function now(input) {
|
|
5009
|
+
const date = input ? new Date(input) : /* @__PURE__ */ new Date();
|
|
5010
|
+
return { date, timestamp: createTimestamp(date) };
|
|
5011
|
+
}
|
|
5012
|
+
var InMemoryRuntimeStateStore = class {
|
|
5013
|
+
agents = /* @__PURE__ */ new Map();
|
|
5014
|
+
messages = /* @__PURE__ */ new Map();
|
|
5015
|
+
runs = /* @__PURE__ */ new Map();
|
|
5016
|
+
toolCalls = /* @__PURE__ */ new Map();
|
|
5017
|
+
events = [];
|
|
5018
|
+
eventCheckpoints = /* @__PURE__ */ new Map();
|
|
5019
|
+
async createAgent(input) {
|
|
5020
|
+
const { timestamp } = now();
|
|
5021
|
+
const agent = {
|
|
5022
|
+
id: createId("agt"),
|
|
5023
|
+
sessionId: input.sessionId,
|
|
5024
|
+
...input.factoryId ? { factoryId: input.factoryId } : {},
|
|
5025
|
+
state: "active",
|
|
5026
|
+
createdAt: timestamp,
|
|
5027
|
+
updatedAt: timestamp
|
|
5028
|
+
};
|
|
5029
|
+
this.agents.set(agent.id, agent);
|
|
5030
|
+
this.recordEvent("agent_created", { agentId: agent.id });
|
|
5031
|
+
return clone2(agent);
|
|
5032
|
+
}
|
|
5033
|
+
async getAgent(agentId) {
|
|
5034
|
+
const agent = this.agents.get(agentId);
|
|
5035
|
+
return agent ? clone2(agent) : void 0;
|
|
5036
|
+
}
|
|
5037
|
+
async listAgents() {
|
|
5038
|
+
return clone2([...this.agents.values()]);
|
|
5039
|
+
}
|
|
5040
|
+
async setAgentState(agentId, state) {
|
|
5041
|
+
const agent = this.requireAgent(agentId);
|
|
5042
|
+
if (agent.state === state) return clone2(agent);
|
|
5043
|
+
const { timestamp } = now();
|
|
5044
|
+
agent.state = state;
|
|
5045
|
+
agent.updatedAt = timestamp;
|
|
5046
|
+
this.recordEvent(state === "paused" ? "agent_paused" : "agent_resumed", { agentId });
|
|
5047
|
+
return clone2(agent);
|
|
5048
|
+
}
|
|
5049
|
+
async enqueueAgentInput(input) {
|
|
5050
|
+
this.requireAgent(input.agentId);
|
|
5051
|
+
const suspended = [...this.runs.values()].filter((run2) => run2.agentId === input.agentId && run2.state === "suspended").sort((a, b) => a.createdAt.localeCompare(b.createdAt))[0];
|
|
5052
|
+
const { timestamp } = now();
|
|
5053
|
+
const messageId = createId("rmsg");
|
|
5054
|
+
const runId = suspended?.id ?? createId("run");
|
|
5055
|
+
const message = {
|
|
5056
|
+
id: messageId,
|
|
5057
|
+
agentId: input.agentId,
|
|
5058
|
+
kind: "agent_input",
|
|
5059
|
+
input: clone2(input.input),
|
|
5060
|
+
state: "queued",
|
|
5061
|
+
attempts: 0,
|
|
5062
|
+
runId,
|
|
5063
|
+
createdAt: timestamp,
|
|
5064
|
+
updatedAt: timestamp
|
|
5065
|
+
};
|
|
5066
|
+
if (suspended) {
|
|
5067
|
+
suspended.state = "queued";
|
|
5068
|
+
suspended.messageId = messageId;
|
|
5069
|
+
delete suspended.suspensionReason;
|
|
5070
|
+
suspended.updatedAt = timestamp;
|
|
5071
|
+
message.runId = suspended.id;
|
|
5072
|
+
}
|
|
5073
|
+
const run = {
|
|
5074
|
+
id: runId,
|
|
5075
|
+
agentId: input.agentId,
|
|
5076
|
+
messageId,
|
|
5077
|
+
state: "queued",
|
|
5078
|
+
attempt: 0,
|
|
5079
|
+
createdAt: timestamp,
|
|
5080
|
+
updatedAt: timestamp
|
|
5081
|
+
};
|
|
5082
|
+
this.messages.set(message.id, message);
|
|
5083
|
+
this.runs.set(run.id, suspended ?? run);
|
|
5084
|
+
this.recordEvent("message_enqueued", { agentId: input.agentId, messageId });
|
|
5085
|
+
this.recordEvent("run_enqueued", { agentId: input.agentId, messageId, runId });
|
|
5086
|
+
return { message: clone2(message), run: clone2(suspended ?? run), resumed: Boolean(suspended) };
|
|
5087
|
+
}
|
|
5088
|
+
async getMessage(messageId) {
|
|
5089
|
+
const message = this.messages.get(messageId);
|
|
5090
|
+
return message ? clone2(message) : void 0;
|
|
5091
|
+
}
|
|
5092
|
+
async getRun(runId) {
|
|
5093
|
+
const run = this.runs.get(runId);
|
|
5094
|
+
return run ? clone2(run) : void 0;
|
|
5095
|
+
}
|
|
5096
|
+
async listRuns(input = {}) {
|
|
5097
|
+
return clone2([...this.runs.values()].filter((run) => input.agentId === void 0 || run.agentId === input.agentId).filter((run) => !input.states || input.states.includes(run.state)).sort((a, b) => a.createdAt.localeCompare(b.createdAt)));
|
|
5098
|
+
}
|
|
5099
|
+
async leaseRun(input) {
|
|
5100
|
+
const run = this.requireRun(input.runId);
|
|
5101
|
+
requireTransition("Agent Run", run.id, run.state, "lease", ["queued"]);
|
|
5102
|
+
const message = this.requireMessage(run.messageId);
|
|
5103
|
+
requireTransition("Runtime Message", message.id, message.state, "lease", ["queued"]);
|
|
5104
|
+
const agent = this.requireAgent(run.agentId);
|
|
5105
|
+
if (agent.state !== "active") {
|
|
5106
|
+
throw transitionError("Agent", agent.id, agent.state, "lease a Run for");
|
|
5107
|
+
}
|
|
5108
|
+
if ([...this.runs.values()].some(
|
|
5109
|
+
(candidate) => candidate.agentId === run.agentId && candidate.id !== run.id && (candidate.state === "running" || candidate.state === "suspended")
|
|
5110
|
+
)) {
|
|
5111
|
+
throw new Error(`Agent ${run.agentId} already has an active Run.`);
|
|
5112
|
+
}
|
|
5113
|
+
if (!Number.isFinite(input.leaseDurationMs) || input.leaseDurationMs <= 0) {
|
|
5114
|
+
throw new Error("Lease duration must be a positive finite number.");
|
|
5115
|
+
}
|
|
5116
|
+
const { date, timestamp } = now(input.now);
|
|
5117
|
+
const lease = {
|
|
5118
|
+
id: createId("lease"),
|
|
5119
|
+
runId: run.id,
|
|
5120
|
+
workerId: input.workerId,
|
|
5121
|
+
leasedAt: timestamp,
|
|
5122
|
+
expiresAt: createTimestamp(new Date(date.getTime() + input.leaseDurationMs))
|
|
5123
|
+
};
|
|
5124
|
+
run.state = "running";
|
|
5125
|
+
run.attempt += 1;
|
|
5126
|
+
run.leaseId = lease.id;
|
|
5127
|
+
run.updatedAt = timestamp;
|
|
5128
|
+
message.state = "leased";
|
|
5129
|
+
message.attempts += 1;
|
|
5130
|
+
message.lease = lease;
|
|
5131
|
+
message.updatedAt = timestamp;
|
|
5132
|
+
this.recordEvent("run_leased", { agentId: run.agentId, messageId: message.id, runId: run.id }, lease);
|
|
5133
|
+
return { run: clone2(run), message: clone2(message), lease: clone2(lease) };
|
|
5134
|
+
}
|
|
5135
|
+
async renewLease(input) {
|
|
5136
|
+
const run = this.requireRun(input.runId);
|
|
5137
|
+
requireTransition("Agent Run", run.id, run.state, "renew Lease", ["running"]);
|
|
5138
|
+
const message = this.requireMessage(run.messageId);
|
|
5139
|
+
requireTransition("Runtime Message", message.id, message.state, "renew Lease", ["leased"]);
|
|
5140
|
+
if (run.leaseId !== input.leaseId || message.lease?.id !== input.leaseId) {
|
|
5141
|
+
throw new Error(`Lease ${input.leaseId} does not own Agent Run ${run.id}.`);
|
|
5142
|
+
}
|
|
5143
|
+
if (!Number.isFinite(input.leaseDurationMs) || input.leaseDurationMs <= 0) {
|
|
5144
|
+
throw new Error("Lease duration must be a positive finite number.");
|
|
5145
|
+
}
|
|
5146
|
+
const { date, timestamp } = now(input.now);
|
|
5147
|
+
const lease = {
|
|
5148
|
+
...message.lease,
|
|
5149
|
+
expiresAt: createTimestamp(new Date(date.getTime() + input.leaseDurationMs))
|
|
5150
|
+
};
|
|
5151
|
+
message.lease = lease;
|
|
5152
|
+
message.updatedAt = timestamp;
|
|
5153
|
+
return clone2(lease);
|
|
5154
|
+
}
|
|
5155
|
+
async suspendRun(input) {
|
|
5156
|
+
const run = this.requireRun(input.runId);
|
|
5157
|
+
requireTransition("Agent Run", run.id, run.state, "suspend", ["running"]);
|
|
5158
|
+
const message = this.requireMessage(run.messageId);
|
|
5159
|
+
const { timestamp } = now();
|
|
5160
|
+
run.state = "suspended";
|
|
5161
|
+
run.suspensionReason = input.reason;
|
|
5162
|
+
delete run.leaseId;
|
|
5163
|
+
run.updatedAt = timestamp;
|
|
5164
|
+
requireTransition("Runtime Message", message.id, message.state, "acknowledge", ["leased"]);
|
|
5165
|
+
message.state = "acknowledged";
|
|
5166
|
+
delete message.lease;
|
|
5167
|
+
message.updatedAt = timestamp;
|
|
5168
|
+
this.recordEvent("run_suspended", { agentId: run.agentId, messageId: message.id, runId: run.id }, {
|
|
5169
|
+
reason: input.reason
|
|
5170
|
+
});
|
|
5171
|
+
return clone2(run);
|
|
5172
|
+
}
|
|
5173
|
+
async completeRun(input) {
|
|
5174
|
+
const run = this.requireRun(input.runId);
|
|
5175
|
+
requireTransition("Agent Run", run.id, run.state, "complete", ["running"]);
|
|
5176
|
+
const message = this.requireMessage(run.messageId);
|
|
5177
|
+
requireTransition("Runtime Message", message.id, message.state, "acknowledge", ["leased"]);
|
|
5178
|
+
const { timestamp } = now();
|
|
5179
|
+
const state = input.state ?? "completed";
|
|
5180
|
+
run.state = state;
|
|
5181
|
+
run.outcome = clone2(input.outcome);
|
|
5182
|
+
delete run.leaseId;
|
|
5183
|
+
run.updatedAt = timestamp;
|
|
5184
|
+
message.state = "acknowledged";
|
|
5185
|
+
delete message.lease;
|
|
5186
|
+
message.updatedAt = timestamp;
|
|
5187
|
+
this.recordEvent("run_completed", { agentId: run.agentId, messageId: run.messageId, runId: run.id }, {
|
|
5188
|
+
state,
|
|
5189
|
+
outcome: input.outcome
|
|
5190
|
+
});
|
|
5191
|
+
this.recordEvent("message_acknowledged", { agentId: message.agentId, messageId: message.id });
|
|
5192
|
+
return clone2(run);
|
|
5193
|
+
}
|
|
5194
|
+
async retryRun(input) {
|
|
5195
|
+
const run = this.requireRun(input.runId);
|
|
5196
|
+
requireTransition("Agent Run", run.id, run.state, "retry", ["running"]);
|
|
5197
|
+
const message = this.requireMessage(run.messageId);
|
|
5198
|
+
requireTransition("Runtime Message", message.id, message.state, "retry", ["leased"]);
|
|
5199
|
+
const { timestamp } = now();
|
|
5200
|
+
run.state = "queued";
|
|
5201
|
+
delete run.leaseId;
|
|
5202
|
+
run.updatedAt = timestamp;
|
|
5203
|
+
message.state = "queued";
|
|
5204
|
+
delete message.lease;
|
|
5205
|
+
message.updatedAt = timestamp;
|
|
5206
|
+
this.recordEvent("run_retry_scheduled", {
|
|
5207
|
+
agentId: run.agentId,
|
|
5208
|
+
messageId: message.id,
|
|
5209
|
+
runId: run.id
|
|
5210
|
+
}, { reason: input.reason, attempt: run.attempt });
|
|
5211
|
+
return clone2(run);
|
|
5212
|
+
}
|
|
5213
|
+
async exhaustRun(input) {
|
|
5214
|
+
const run = this.requireRun(input.runId);
|
|
5215
|
+
requireTransition("Agent Run", run.id, run.state, "exhaust retries", ["running"]);
|
|
5216
|
+
const message = this.requireMessage(run.messageId);
|
|
5217
|
+
requireTransition("Runtime Message", message.id, message.state, "dead-letter", ["leased"]);
|
|
5218
|
+
const { timestamp } = now();
|
|
5219
|
+
run.state = "failed";
|
|
5220
|
+
run.outcome = clone2(input.outcome);
|
|
5221
|
+
delete run.leaseId;
|
|
5222
|
+
run.updatedAt = timestamp;
|
|
5223
|
+
message.state = "dead_lettered";
|
|
5224
|
+
message.deadLetterReason = input.reason;
|
|
5225
|
+
delete message.lease;
|
|
5226
|
+
message.updatedAt = timestamp;
|
|
5227
|
+
this.recordEvent("run_completed", { agentId: run.agentId, messageId: message.id, runId: run.id }, {
|
|
5228
|
+
state: "failed",
|
|
5229
|
+
outcome: input.outcome
|
|
5230
|
+
});
|
|
5231
|
+
this.recordEvent("message_dead_lettered", { agentId: run.agentId, messageId: message.id }, {
|
|
5232
|
+
reason: input.reason
|
|
5233
|
+
});
|
|
5234
|
+
return clone2(run);
|
|
5235
|
+
}
|
|
5236
|
+
async abortRun(input) {
|
|
5237
|
+
const run = this.requireRun(input.runId);
|
|
5238
|
+
requireTransition("Agent Run", run.id, run.state, "abort", ["queued", "running", "suspended"]);
|
|
5239
|
+
const message = this.requireMessage(run.messageId);
|
|
5240
|
+
const { timestamp } = now();
|
|
5241
|
+
run.state = "cancelled";
|
|
5242
|
+
run.outcome = clone2(input.outcome);
|
|
5243
|
+
delete run.leaseId;
|
|
5244
|
+
run.updatedAt = timestamp;
|
|
5245
|
+
if (message.state === "queued" || message.state === "leased") {
|
|
5246
|
+
message.state = "acknowledged";
|
|
5247
|
+
delete message.lease;
|
|
5248
|
+
message.updatedAt = timestamp;
|
|
5249
|
+
}
|
|
5250
|
+
this.recordEvent("run_aborted", { agentId: run.agentId, messageId: message.id, runId: run.id }, {
|
|
5251
|
+
outcome: input.outcome
|
|
5252
|
+
});
|
|
5253
|
+
return clone2(run);
|
|
5254
|
+
}
|
|
5255
|
+
async recoverExpiredLeases(input) {
|
|
5256
|
+
const { date, timestamp } = now(input);
|
|
5257
|
+
const recovered = [];
|
|
5258
|
+
for (const run of this.runs.values()) {
|
|
5259
|
+
if (run.state !== "running" || !run.leaseId) continue;
|
|
5260
|
+
const message = this.requireMessage(run.messageId);
|
|
5261
|
+
if (message.state !== "leased" || !message.lease || Date.parse(message.lease.expiresAt) > date.getTime()) continue;
|
|
5262
|
+
run.state = "queued";
|
|
5263
|
+
delete run.leaseId;
|
|
5264
|
+
run.updatedAt = timestamp;
|
|
5265
|
+
message.state = "queued";
|
|
5266
|
+
delete message.lease;
|
|
5267
|
+
message.updatedAt = timestamp;
|
|
5268
|
+
this.recordEvent("lease_expired", { agentId: run.agentId, messageId: message.id, runId: run.id });
|
|
5269
|
+
recovered.push(clone2(run));
|
|
5270
|
+
}
|
|
5271
|
+
return recovered;
|
|
5272
|
+
}
|
|
5273
|
+
async recoverLeases() {
|
|
5274
|
+
const { timestamp } = now();
|
|
5275
|
+
const recovered = [];
|
|
5276
|
+
for (const run of this.runs.values()) {
|
|
5277
|
+
if (run.state !== "running" || !run.leaseId) continue;
|
|
5278
|
+
const message = this.requireMessage(run.messageId);
|
|
5279
|
+
if (message.state !== "leased" || !message.lease) continue;
|
|
5280
|
+
run.state = "queued";
|
|
5281
|
+
delete run.leaseId;
|
|
5282
|
+
run.updatedAt = timestamp;
|
|
5283
|
+
message.state = "queued";
|
|
5284
|
+
delete message.lease;
|
|
5285
|
+
message.updatedAt = timestamp;
|
|
5286
|
+
this.recordEvent("lease_recovered", { agentId: run.agentId, messageId: message.id, runId: run.id });
|
|
5287
|
+
recovered.push(clone2(run));
|
|
5288
|
+
}
|
|
5289
|
+
return recovered;
|
|
5290
|
+
}
|
|
5291
|
+
async createToolCall(input) {
|
|
5292
|
+
this.requireAgent(input.agentId);
|
|
5293
|
+
this.requireRun(input.runId);
|
|
5294
|
+
const { timestamp } = now();
|
|
5295
|
+
const toolCall = {
|
|
5296
|
+
id: createId("call"),
|
|
5297
|
+
agentId: input.agentId,
|
|
5298
|
+
runId: input.runId,
|
|
5299
|
+
name: input.name,
|
|
5300
|
+
args: clone2(input.args),
|
|
5301
|
+
state: "queued",
|
|
5302
|
+
createdAt: timestamp,
|
|
5303
|
+
updatedAt: timestamp
|
|
5304
|
+
};
|
|
5305
|
+
this.toolCalls.set(toolCall.id, toolCall);
|
|
5306
|
+
this.recordEvent("tool_call_created", { agentId: input.agentId, runId: input.runId, toolCallId: toolCall.id });
|
|
5307
|
+
return clone2(toolCall);
|
|
5308
|
+
}
|
|
5309
|
+
async startToolCall(toolCallId) {
|
|
5310
|
+
const toolCall = this.requireToolCall(toolCallId);
|
|
5311
|
+
requireTransition("Tool Call", toolCall.id, toolCall.state, "start", ["queued"]);
|
|
5312
|
+
const { timestamp } = now();
|
|
5313
|
+
toolCall.state = "running";
|
|
5314
|
+
toolCall.updatedAt = timestamp;
|
|
5315
|
+
this.recordEvent("tool_call_started", { agentId: toolCall.agentId, runId: toolCall.runId, toolCallId: toolCall.id });
|
|
5316
|
+
return clone2(toolCall);
|
|
5317
|
+
}
|
|
5318
|
+
async completeToolCall(input) {
|
|
5319
|
+
const toolCall = this.requireToolCall(input.toolCallId);
|
|
5320
|
+
const state = input.state ?? (input.result.ok ? "completed" : "failed");
|
|
5321
|
+
requireTransition(
|
|
5322
|
+
"Tool Call",
|
|
5323
|
+
toolCall.id,
|
|
5324
|
+
toolCall.state,
|
|
5325
|
+
"complete",
|
|
5326
|
+
state === "failed" ? ["queued", "running"] : ["running"]
|
|
4480
5327
|
);
|
|
5328
|
+
const { timestamp } = now();
|
|
5329
|
+
toolCall.state = state;
|
|
5330
|
+
toolCall.result = clone2(input.result);
|
|
5331
|
+
toolCall.updatedAt = timestamp;
|
|
5332
|
+
this.recordEvent(state === "failed" ? "tool_call_failed" : "tool_call_completed", {
|
|
5333
|
+
agentId: toolCall.agentId,
|
|
5334
|
+
runId: toolCall.runId,
|
|
5335
|
+
toolCallId: toolCall.id
|
|
5336
|
+
}, { state: toolCall.state });
|
|
5337
|
+
return clone2(toolCall);
|
|
5338
|
+
}
|
|
5339
|
+
async markToolCallIndeterminate(input) {
|
|
5340
|
+
const toolCall = this.requireToolCall(input.toolCallId);
|
|
5341
|
+
requireTransition("Tool Call", toolCall.id, toolCall.state, "mark indeterminate", ["running"]);
|
|
5342
|
+
const { timestamp } = now();
|
|
5343
|
+
toolCall.state = "indeterminate";
|
|
5344
|
+
toolCall.indeterminateReason = input.reason;
|
|
5345
|
+
toolCall.updatedAt = timestamp;
|
|
5346
|
+
this.recordEvent("tool_call_indeterminate", {
|
|
5347
|
+
agentId: toolCall.agentId,
|
|
5348
|
+
runId: toolCall.runId,
|
|
5349
|
+
toolCallId: toolCall.id
|
|
5350
|
+
}, { reason: input.reason });
|
|
5351
|
+
return clone2(toolCall);
|
|
5352
|
+
}
|
|
5353
|
+
async getToolCall(toolCallId) {
|
|
5354
|
+
const toolCall = this.toolCalls.get(toolCallId);
|
|
5355
|
+
return toolCall ? clone2(toolCall) : void 0;
|
|
5356
|
+
}
|
|
5357
|
+
async listEvents(cursor = {}) {
|
|
5358
|
+
let events = this.events;
|
|
5359
|
+
if (cursor.after) {
|
|
5360
|
+
const index = events.findIndex((event) => event.id === cursor.after);
|
|
5361
|
+
if (index >= 0) events = events.slice(index + 1);
|
|
5362
|
+
}
|
|
5363
|
+
return clone2(cursor.limit === void 0 ? events : events.slice(0, cursor.limit));
|
|
5364
|
+
}
|
|
5365
|
+
async recordEvent(input, related = {}, payload) {
|
|
5366
|
+
this.recordEventInternal(typeof input === "string" ? input : input.kind, typeof input === "string" ? related : input.related ?? {}, typeof input === "string" ? payload : input.payload);
|
|
5367
|
+
return clone2(this.events.at(-1));
|
|
5368
|
+
}
|
|
5369
|
+
async getEventCheckpoint(consumerId) {
|
|
5370
|
+
return clone2(this.eventCheckpoints.get(consumerId) ?? {
|
|
5371
|
+
consumerId,
|
|
5372
|
+
sequence: 0,
|
|
5373
|
+
updatedAt: createTimestamp(/* @__PURE__ */ new Date(0))
|
|
5374
|
+
});
|
|
5375
|
+
}
|
|
5376
|
+
async acknowledgeEvent(consumerId, eventId) {
|
|
5377
|
+
const event = this.events.find((candidate) => candidate.id === eventId);
|
|
5378
|
+
if (!event) throw new Error(`Runtime Event not found: ${eventId}.`);
|
|
5379
|
+
const current = await this.getEventCheckpoint(consumerId);
|
|
5380
|
+
if (event.sequence <= current.sequence) return current;
|
|
5381
|
+
if (event.sequence !== current.sequence + 1) {
|
|
5382
|
+
throw new Error(`Runtime Event Consumer Checkpoint cannot advance from sequence ${current.sequence} to ${event.sequence}.`);
|
|
5383
|
+
}
|
|
5384
|
+
const checkpoint = {
|
|
5385
|
+
consumerId,
|
|
5386
|
+
sequence: event.sequence,
|
|
5387
|
+
eventId: event.id,
|
|
5388
|
+
updatedAt: createTimestamp()
|
|
5389
|
+
};
|
|
5390
|
+
this.eventCheckpoints.set(consumerId, checkpoint);
|
|
5391
|
+
return clone2(checkpoint);
|
|
5392
|
+
}
|
|
5393
|
+
requireAgent(agentId) {
|
|
5394
|
+
const agent = this.agents.get(agentId);
|
|
5395
|
+
if (!agent) {
|
|
5396
|
+
throw new Error(`Agent not found: ${agentId}.`);
|
|
5397
|
+
}
|
|
5398
|
+
return agent;
|
|
5399
|
+
}
|
|
5400
|
+
requireMessage(messageId) {
|
|
5401
|
+
const message = this.messages.get(messageId);
|
|
5402
|
+
if (!message) {
|
|
5403
|
+
throw new Error(`Runtime Message not found: ${messageId}.`);
|
|
5404
|
+
}
|
|
5405
|
+
return message;
|
|
5406
|
+
}
|
|
5407
|
+
requireRun(runId) {
|
|
5408
|
+
const run = this.runs.get(runId);
|
|
5409
|
+
if (!run) {
|
|
5410
|
+
throw new Error(`Agent Run not found: ${runId}.`);
|
|
5411
|
+
}
|
|
5412
|
+
return run;
|
|
5413
|
+
}
|
|
5414
|
+
requireToolCall(toolCallId) {
|
|
5415
|
+
const toolCall = this.toolCalls.get(toolCallId);
|
|
5416
|
+
if (!toolCall) {
|
|
5417
|
+
throw new Error(`Tool Call not found: ${toolCallId}.`);
|
|
5418
|
+
}
|
|
5419
|
+
return toolCall;
|
|
5420
|
+
}
|
|
5421
|
+
recordEventInternal(kind, related, payload) {
|
|
5422
|
+
this.events.push({
|
|
5423
|
+
id: createId("evt"),
|
|
5424
|
+
sequence: this.events.length + 1,
|
|
5425
|
+
kind,
|
|
5426
|
+
...related,
|
|
5427
|
+
...payload !== void 0 ? { payload: clone2(payload) } : {},
|
|
5428
|
+
createdAt: createTimestamp()
|
|
5429
|
+
});
|
|
4481
5430
|
}
|
|
4482
5431
|
};
|
|
4483
5432
|
|
|
4484
|
-
// src/
|
|
4485
|
-
import {
|
|
4486
|
-
|
|
4487
|
-
|
|
4488
|
-
|
|
5433
|
+
// src/runtime/sqlite-store.ts
|
|
5434
|
+
import { Database } from "bun:sqlite";
|
|
5435
|
+
|
|
5436
|
+
// src/runtime/runtime-schema.ts
|
|
5437
|
+
var RUNTIME_SCHEMA_SQL = `
|
|
5438
|
+
CREATE TABLE IF NOT EXISTS agents (
|
|
5439
|
+
id TEXT PRIMARY KEY NOT NULL,
|
|
5440
|
+
session_id TEXT NOT NULL,
|
|
5441
|
+
factory_id TEXT,
|
|
5442
|
+
state TEXT NOT NULL CHECK (state IN ('active', 'paused')),
|
|
5443
|
+
created_at TEXT NOT NULL,
|
|
5444
|
+
updated_at TEXT NOT NULL
|
|
5445
|
+
);
|
|
5446
|
+
|
|
5447
|
+
CREATE TABLE IF NOT EXISTS runtime_messages (
|
|
5448
|
+
id TEXT PRIMARY KEY NOT NULL,
|
|
5449
|
+
agent_id TEXT NOT NULL REFERENCES agents(id),
|
|
5450
|
+
kind TEXT NOT NULL CHECK (kind IN ('agent_input')),
|
|
5451
|
+
payload_json TEXT NOT NULL,
|
|
5452
|
+
state TEXT NOT NULL CHECK (state IN ('queued', 'leased', 'acknowledged', 'dead_lettered')),
|
|
5453
|
+
attempts INTEGER NOT NULL DEFAULT 0,
|
|
5454
|
+
run_id TEXT,
|
|
5455
|
+
dead_letter_reason TEXT,
|
|
5456
|
+
created_at TEXT NOT NULL,
|
|
5457
|
+
updated_at TEXT NOT NULL
|
|
5458
|
+
);
|
|
5459
|
+
|
|
5460
|
+
CREATE TABLE IF NOT EXISTS agent_runs (
|
|
5461
|
+
id TEXT PRIMARY KEY NOT NULL,
|
|
5462
|
+
agent_id TEXT NOT NULL REFERENCES agents(id),
|
|
5463
|
+
message_id TEXT NOT NULL REFERENCES runtime_messages(id),
|
|
5464
|
+
state TEXT NOT NULL CHECK (state IN ('queued', 'running', 'suspended', 'completed', 'failed', 'cancelled')),
|
|
5465
|
+
attempt INTEGER NOT NULL DEFAULT 0,
|
|
5466
|
+
lease_id TEXT,
|
|
5467
|
+
outcome_json TEXT,
|
|
5468
|
+
suspension_reason TEXT,
|
|
5469
|
+
created_at TEXT NOT NULL,
|
|
5470
|
+
updated_at TEXT NOT NULL
|
|
5471
|
+
);
|
|
5472
|
+
|
|
5473
|
+
CREATE TABLE IF NOT EXISTS runtime_leases (
|
|
5474
|
+
id TEXT PRIMARY KEY NOT NULL,
|
|
5475
|
+
run_id TEXT NOT NULL UNIQUE REFERENCES agent_runs(id),
|
|
5476
|
+
worker_id TEXT NOT NULL,
|
|
5477
|
+
leased_at TEXT NOT NULL,
|
|
5478
|
+
expires_at TEXT NOT NULL
|
|
5479
|
+
);
|
|
5480
|
+
|
|
5481
|
+
CREATE TABLE IF NOT EXISTS runtime_events (
|
|
5482
|
+
id TEXT PRIMARY KEY NOT NULL,
|
|
5483
|
+
sequence INTEGER NOT NULL DEFAULT 0,
|
|
5484
|
+
kind TEXT NOT NULL,
|
|
5485
|
+
agent_id TEXT,
|
|
5486
|
+
message_id TEXT,
|
|
5487
|
+
run_id TEXT,
|
|
5488
|
+
tool_call_id TEXT,
|
|
5489
|
+
payload_json TEXT,
|
|
5490
|
+
created_at TEXT NOT NULL
|
|
5491
|
+
);
|
|
5492
|
+
|
|
5493
|
+
CREATE TABLE IF NOT EXISTS runtime_tool_calls (
|
|
5494
|
+
id TEXT PRIMARY KEY NOT NULL,
|
|
5495
|
+
agent_id TEXT NOT NULL REFERENCES agents(id),
|
|
5496
|
+
run_id TEXT NOT NULL REFERENCES agent_runs(id),
|
|
5497
|
+
name TEXT NOT NULL,
|
|
5498
|
+
args_json TEXT NOT NULL,
|
|
5499
|
+
state TEXT NOT NULL CHECK (state IN ('queued', 'running', 'completed', 'failed', 'indeterminate')),
|
|
5500
|
+
result_json TEXT,
|
|
5501
|
+
indeterminate_reason TEXT,
|
|
5502
|
+
created_at TEXT NOT NULL,
|
|
5503
|
+
updated_at TEXT NOT NULL
|
|
5504
|
+
);
|
|
5505
|
+
|
|
5506
|
+
CREATE TABLE IF NOT EXISTS runtime_event_consumers (
|
|
5507
|
+
consumer_id TEXT PRIMARY KEY NOT NULL,
|
|
5508
|
+
sequence INTEGER NOT NULL DEFAULT 0,
|
|
5509
|
+
event_id TEXT,
|
|
5510
|
+
updated_at TEXT NOT NULL
|
|
5511
|
+
);
|
|
5512
|
+
|
|
5513
|
+
CREATE INDEX IF NOT EXISTS runtime_messages_runnable_idx
|
|
5514
|
+
ON runtime_messages (agent_id, state, created_at);
|
|
5515
|
+
CREATE INDEX IF NOT EXISTS agent_runs_agent_state_idx
|
|
5516
|
+
ON agent_runs (agent_id, state, created_at);
|
|
5517
|
+
CREATE INDEX IF NOT EXISTS runtime_events_delivery_idx
|
|
5518
|
+
ON runtime_events (sequence);
|
|
5519
|
+
|
|
5520
|
+
CREATE INDEX IF NOT EXISTS agent_runs_runnable_idx
|
|
5521
|
+
ON agent_runs (state, created_at);
|
|
5522
|
+
`;
|
|
5523
|
+
function initializeRuntimeSchema(database) {
|
|
5524
|
+
const initialize = database.transaction(() => {
|
|
5525
|
+
for (const statement of RUNTIME_SCHEMA_SQL.split(";")) {
|
|
5526
|
+
const sql = statement.trim();
|
|
5527
|
+
if (sql.length > 0) {
|
|
5528
|
+
database.run(sql);
|
|
5529
|
+
}
|
|
5530
|
+
}
|
|
5531
|
+
});
|
|
5532
|
+
initialize();
|
|
5533
|
+
}
|
|
5534
|
+
|
|
5535
|
+
// src/runtime/sqlite-store.ts
|
|
5536
|
+
function clone3(value) {
|
|
5537
|
+
return structuredClone(value);
|
|
5538
|
+
}
|
|
5539
|
+
function json(value) {
|
|
5540
|
+
return JSON.stringify(value);
|
|
5541
|
+
}
|
|
5542
|
+
function parse(value) {
|
|
5543
|
+
return JSON.parse(value);
|
|
5544
|
+
}
|
|
5545
|
+
function optionalJson(value) {
|
|
5546
|
+
return value === null ? void 0 : parse(value);
|
|
5547
|
+
}
|
|
5548
|
+
function transitionError2(entity, id, state, action) {
|
|
5549
|
+
return new Error(`Cannot ${action} ${entity} ${id} from state "${state}".`);
|
|
5550
|
+
}
|
|
5551
|
+
function requireTransition2(entity, id, state, action, allowedStates) {
|
|
5552
|
+
if (!allowedStates.includes(state)) {
|
|
5553
|
+
throw transitionError2(entity, id, state, action);
|
|
5554
|
+
}
|
|
5555
|
+
}
|
|
5556
|
+
function timestampFor(input) {
|
|
5557
|
+
const date = input ? new Date(input) : /* @__PURE__ */ new Date();
|
|
5558
|
+
return { date, timestamp: createTimestamp(date) };
|
|
5559
|
+
}
|
|
5560
|
+
var SqliteRuntimeStateStore = class {
|
|
5561
|
+
database;
|
|
5562
|
+
constructor(filename = ":memory:") {
|
|
5563
|
+
this.database = new Database(filename, { create: true, readwrite: true, strict: true });
|
|
5564
|
+
this.database.run("PRAGMA foreign_keys = ON");
|
|
5565
|
+
initializeRuntimeSchema(this.database);
|
|
5566
|
+
}
|
|
5567
|
+
close() {
|
|
5568
|
+
this.database.close();
|
|
5569
|
+
}
|
|
5570
|
+
async createAgent(input) {
|
|
5571
|
+
const { timestamp } = timestampFor();
|
|
5572
|
+
const agent = {
|
|
5573
|
+
id: createId("agt"),
|
|
5574
|
+
sessionId: input.sessionId,
|
|
5575
|
+
...input.factoryId ? { factoryId: input.factoryId } : {},
|
|
5576
|
+
state: "active",
|
|
5577
|
+
createdAt: timestamp,
|
|
5578
|
+
updatedAt: timestamp
|
|
5579
|
+
};
|
|
5580
|
+
const create = this.database.transaction(() => {
|
|
5581
|
+
this.database.run(
|
|
5582
|
+
"INSERT INTO agents (id, session_id, factory_id, state, created_at, updated_at) VALUES (?, ?, ?, ?, ?, ?)",
|
|
5583
|
+
[agent.id, agent.sessionId, agent.factoryId ?? null, agent.state, agent.createdAt, agent.updatedAt]
|
|
5584
|
+
);
|
|
5585
|
+
this.recordEvent("agent_created", { agentId: agent.id });
|
|
5586
|
+
});
|
|
5587
|
+
create();
|
|
5588
|
+
return clone3(agent);
|
|
5589
|
+
}
|
|
5590
|
+
async getAgent(agentId) {
|
|
5591
|
+
const row = this.database.query("SELECT * FROM agents WHERE id = ?").get(agentId);
|
|
5592
|
+
return row ? this.agentFromRow(row) : void 0;
|
|
5593
|
+
}
|
|
5594
|
+
async listAgents() {
|
|
5595
|
+
const rows = this.database.query("SELECT * FROM agents ORDER BY created_at, id").all();
|
|
5596
|
+
return rows.map((row) => this.agentFromRow(row));
|
|
5597
|
+
}
|
|
5598
|
+
async setAgentState(agentId, state) {
|
|
5599
|
+
const current = this.requireAgent(agentId);
|
|
5600
|
+
if (current.state === state) return clone3(current);
|
|
5601
|
+
const { timestamp } = timestampFor();
|
|
5602
|
+
const updated = this.database.transaction(() => {
|
|
5603
|
+
this.database.run("UPDATE agents SET state = ?, updated_at = ? WHERE id = ?", [state, timestamp, agentId]);
|
|
5604
|
+
this.recordEvent(state === "paused" ? "agent_paused" : "agent_resumed", { agentId });
|
|
5605
|
+
return this.requireAgent(agentId);
|
|
5606
|
+
})();
|
|
5607
|
+
return clone3(updated);
|
|
5608
|
+
}
|
|
5609
|
+
async enqueueAgentInput(input) {
|
|
5610
|
+
this.requireAgent(input.agentId);
|
|
5611
|
+
const { timestamp } = timestampFor();
|
|
5612
|
+
const messageId = createId("rmsg");
|
|
5613
|
+
const suspendedRow = this.database.query(
|
|
5614
|
+
"SELECT * FROM agent_runs WHERE agent_id = ? AND state = 'suspended' ORDER BY created_at, id LIMIT 1"
|
|
5615
|
+
).get(input.agentId);
|
|
5616
|
+
const runId = suspendedRow?.id ?? createId("run");
|
|
5617
|
+
const message = {
|
|
5618
|
+
id: messageId,
|
|
5619
|
+
agentId: input.agentId,
|
|
5620
|
+
kind: "agent_input",
|
|
5621
|
+
input: clone3(input.input),
|
|
5622
|
+
state: "queued",
|
|
5623
|
+
attempts: 0,
|
|
5624
|
+
runId,
|
|
5625
|
+
createdAt: timestamp,
|
|
5626
|
+
updatedAt: timestamp
|
|
5627
|
+
};
|
|
5628
|
+
const run = {
|
|
5629
|
+
id: runId,
|
|
5630
|
+
agentId: input.agentId,
|
|
5631
|
+
messageId,
|
|
5632
|
+
state: "queued",
|
|
5633
|
+
attempt: 0,
|
|
5634
|
+
createdAt: timestamp,
|
|
5635
|
+
updatedAt: timestamp
|
|
5636
|
+
};
|
|
5637
|
+
if (suspendedRow) {
|
|
5638
|
+
message.runId = runId;
|
|
5639
|
+
}
|
|
5640
|
+
const enqueue = this.database.transaction(() => {
|
|
5641
|
+
this.insertMessage(message);
|
|
5642
|
+
if (suspendedRow) {
|
|
5643
|
+
this.database.run(
|
|
5644
|
+
"UPDATE agent_runs SET message_id = ?, state = 'queued', lease_id = NULL, suspension_reason = NULL, updated_at = ? WHERE id = ?",
|
|
5645
|
+
[messageId, timestamp, runId]
|
|
5646
|
+
);
|
|
5647
|
+
} else {
|
|
5648
|
+
this.database.run(
|
|
5649
|
+
"INSERT INTO agent_runs (id, agent_id, message_id, state, attempt, lease_id, outcome_json, suspension_reason, created_at, updated_at) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)",
|
|
5650
|
+
[run.id, run.agentId, run.messageId, run.state, run.attempt, null, null, null, run.createdAt, run.updatedAt]
|
|
5651
|
+
);
|
|
5652
|
+
}
|
|
5653
|
+
this.recordEvent("message_enqueued", { agentId: input.agentId, messageId });
|
|
5654
|
+
this.recordEvent("run_enqueued", { agentId: input.agentId, messageId, runId });
|
|
5655
|
+
});
|
|
5656
|
+
enqueue();
|
|
5657
|
+
const storedRun = this.requireRun(runId);
|
|
5658
|
+
return { message: clone3(message), run: clone3(storedRun), resumed: Boolean(suspendedRow) };
|
|
5659
|
+
}
|
|
5660
|
+
async getMessage(messageId) {
|
|
5661
|
+
const row = this.messageRow(messageId);
|
|
5662
|
+
return row ? this.messageFromRow(row) : void 0;
|
|
5663
|
+
}
|
|
5664
|
+
async getRun(runId) {
|
|
5665
|
+
const row = this.runRow(runId);
|
|
5666
|
+
return row ? this.runFromRow(row) : void 0;
|
|
5667
|
+
}
|
|
5668
|
+
async listRuns(input = {}) {
|
|
5669
|
+
const clauses = [];
|
|
5670
|
+
const params = [];
|
|
5671
|
+
if (input.agentId) {
|
|
5672
|
+
clauses.push("agent_id = ?");
|
|
5673
|
+
params.push(input.agentId);
|
|
5674
|
+
}
|
|
5675
|
+
if (input.states && input.states.length > 0) {
|
|
5676
|
+
clauses.push(`state IN (${input.states.map(() => "?").join(", ")})`);
|
|
5677
|
+
params.push(...input.states);
|
|
5678
|
+
}
|
|
5679
|
+
const where = clauses.length > 0 ? `WHERE ${clauses.join(" AND ")}` : "";
|
|
5680
|
+
const rows = this.database.query(`SELECT * FROM agent_runs ${where} ORDER BY created_at, id`).all(...params);
|
|
5681
|
+
return rows.map((row) => this.runFromRow(row));
|
|
5682
|
+
}
|
|
5683
|
+
async leaseRun(input) {
|
|
5684
|
+
const lease = this.database.transaction(() => {
|
|
5685
|
+
const run2 = this.requireRun(input.runId);
|
|
5686
|
+
requireTransition2("Agent Run", run2.id, run2.state, "lease", ["queued"]);
|
|
5687
|
+
const message2 = this.requireMessage(run2.messageId);
|
|
5688
|
+
requireTransition2("Runtime Message", message2.id, message2.state, "lease", ["queued"]);
|
|
5689
|
+
const agent = this.requireAgent(run2.agentId);
|
|
5690
|
+
if (agent.state !== "active") {
|
|
5691
|
+
throw transitionError2("Agent", agent.id, agent.state, "lease a Run for");
|
|
5692
|
+
}
|
|
5693
|
+
const activeRun = this.database.query(
|
|
5694
|
+
"SELECT id FROM agent_runs WHERE agent_id = ? AND id <> ? AND state IN ('running', 'suspended') LIMIT 1"
|
|
5695
|
+
).get(run2.agentId, run2.id);
|
|
5696
|
+
if (activeRun) {
|
|
5697
|
+
throw new Error(`Agent ${run2.agentId} already has an active Run.`);
|
|
5698
|
+
}
|
|
5699
|
+
if (!Number.isFinite(input.leaseDurationMs) || input.leaseDurationMs <= 0) {
|
|
5700
|
+
throw new Error("Lease duration must be a positive finite number.");
|
|
5701
|
+
}
|
|
5702
|
+
const { date, timestamp } = timestampFor(input.now);
|
|
5703
|
+
const runtimeLease = {
|
|
5704
|
+
id: createId("lease"),
|
|
5705
|
+
runId: run2.id,
|
|
5706
|
+
workerId: input.workerId,
|
|
5707
|
+
leasedAt: timestamp,
|
|
5708
|
+
expiresAt: createTimestamp(new Date(date.getTime() + input.leaseDurationMs))
|
|
5709
|
+
};
|
|
5710
|
+
this.database.run(
|
|
5711
|
+
"INSERT INTO runtime_leases (id, run_id, worker_id, leased_at, expires_at) VALUES (?, ?, ?, ?, ?)",
|
|
5712
|
+
[runtimeLease.id, runtimeLease.runId, runtimeLease.workerId, runtimeLease.leasedAt, runtimeLease.expiresAt]
|
|
5713
|
+
);
|
|
5714
|
+
this.database.run(
|
|
5715
|
+
"UPDATE agent_runs SET state = ?, attempt = attempt + 1, lease_id = ?, updated_at = ? WHERE id = ?",
|
|
5716
|
+
["running", runtimeLease.id, timestamp, run2.id]
|
|
5717
|
+
);
|
|
5718
|
+
this.database.run(
|
|
5719
|
+
"UPDATE runtime_messages SET state = ?, attempts = attempts + 1, updated_at = ? WHERE id = ?",
|
|
5720
|
+
["leased", timestamp, message2.id]
|
|
5721
|
+
);
|
|
5722
|
+
this.recordEvent("run_leased", { agentId: run2.agentId, messageId: message2.id, runId: run2.id }, runtimeLease);
|
|
5723
|
+
return runtimeLease;
|
|
5724
|
+
})();
|
|
5725
|
+
const run = this.requireRun(input.runId);
|
|
5726
|
+
const message = this.requireMessage(run.messageId);
|
|
5727
|
+
return { run, message, lease: clone3(lease) };
|
|
5728
|
+
}
|
|
5729
|
+
async renewLease(input) {
|
|
5730
|
+
const lease = this.database.transaction(() => {
|
|
5731
|
+
const run = this.requireRun(input.runId);
|
|
5732
|
+
requireTransition2("Agent Run", run.id, run.state, "renew Lease", ["running"]);
|
|
5733
|
+
const message = this.requireMessage(run.messageId);
|
|
5734
|
+
requireTransition2("Runtime Message", message.id, message.state, "renew Lease", ["leased"]);
|
|
5735
|
+
if (run.leaseId !== input.leaseId || message.lease?.id !== input.leaseId) {
|
|
5736
|
+
throw new Error(`Lease ${input.leaseId} does not own Agent Run ${run.id}.`);
|
|
5737
|
+
}
|
|
5738
|
+
if (!Number.isFinite(input.leaseDurationMs) || input.leaseDurationMs <= 0) {
|
|
5739
|
+
throw new Error("Lease duration must be a positive finite number.");
|
|
5740
|
+
}
|
|
5741
|
+
const { date, timestamp } = timestampFor(input.now);
|
|
5742
|
+
const expiresAt = createTimestamp(new Date(date.getTime() + input.leaseDurationMs));
|
|
5743
|
+
this.database.run("UPDATE runtime_leases SET expires_at = ? WHERE id = ?", [expiresAt, input.leaseId]);
|
|
5744
|
+
this.database.run("UPDATE runtime_messages SET updated_at = ? WHERE id = ?", [timestamp, message.id]);
|
|
5745
|
+
return { ...message.lease, expiresAt };
|
|
5746
|
+
})();
|
|
5747
|
+
return clone3(lease);
|
|
5748
|
+
}
|
|
5749
|
+
async suspendRun(input) {
|
|
5750
|
+
const run = this.database.transaction(() => {
|
|
5751
|
+
const current = this.requireRun(input.runId);
|
|
5752
|
+
requireTransition2("Agent Run", current.id, current.state, "suspend", ["running"]);
|
|
5753
|
+
const message = this.requireMessage(current.messageId);
|
|
5754
|
+
requireTransition2("Runtime Message", message.id, message.state, "acknowledge", ["leased"]);
|
|
5755
|
+
const { timestamp } = timestampFor();
|
|
5756
|
+
this.database.run(
|
|
5757
|
+
"UPDATE agent_runs SET state = ?, lease_id = NULL, suspension_reason = ?, updated_at = ? WHERE id = ?",
|
|
5758
|
+
["suspended", input.reason ?? null, timestamp, current.id]
|
|
5759
|
+
);
|
|
5760
|
+
this.database.run(
|
|
5761
|
+
"UPDATE runtime_messages SET state = ?, updated_at = ? WHERE id = ?",
|
|
5762
|
+
["acknowledged", timestamp, message.id]
|
|
5763
|
+
);
|
|
5764
|
+
this.database.run("DELETE FROM runtime_leases WHERE run_id = ?", [current.id]);
|
|
5765
|
+
this.recordEvent("run_suspended", { agentId: current.agentId, messageId: message.id, runId: current.id }, {
|
|
5766
|
+
reason: input.reason
|
|
5767
|
+
});
|
|
5768
|
+
return this.requireRun(current.id);
|
|
5769
|
+
})();
|
|
5770
|
+
return clone3(run);
|
|
5771
|
+
}
|
|
5772
|
+
async completeRun(input) {
|
|
5773
|
+
const run = this.database.transaction(() => {
|
|
5774
|
+
const current = this.requireRun(input.runId);
|
|
5775
|
+
requireTransition2("Agent Run", current.id, current.state, "complete", ["running"]);
|
|
5776
|
+
const message = this.requireMessage(current.messageId);
|
|
5777
|
+
requireTransition2("Runtime Message", message.id, message.state, "acknowledge", ["leased"]);
|
|
5778
|
+
const { timestamp } = timestampFor();
|
|
5779
|
+
const state = input.state ?? "completed";
|
|
5780
|
+
this.database.run(
|
|
5781
|
+
"UPDATE agent_runs SET state = ?, lease_id = NULL, outcome_json = ?, updated_at = ? WHERE id = ?",
|
|
5782
|
+
[state, json(input.outcome), timestamp, current.id]
|
|
5783
|
+
);
|
|
5784
|
+
this.database.run(
|
|
5785
|
+
"UPDATE runtime_messages SET state = 'acknowledged', updated_at = ? WHERE id = ?",
|
|
5786
|
+
[timestamp, message.id]
|
|
5787
|
+
);
|
|
5788
|
+
this.database.run("DELETE FROM runtime_leases WHERE run_id = ?", [current.id]);
|
|
5789
|
+
this.recordEvent("run_completed", { agentId: current.agentId, messageId: current.messageId, runId: current.id }, {
|
|
5790
|
+
state,
|
|
5791
|
+
outcome: input.outcome
|
|
5792
|
+
});
|
|
5793
|
+
this.recordEvent("message_acknowledged", { agentId: message.agentId, messageId: message.id });
|
|
5794
|
+
return this.requireRun(current.id);
|
|
5795
|
+
})();
|
|
5796
|
+
return clone3(run);
|
|
5797
|
+
}
|
|
5798
|
+
async retryRun(input) {
|
|
5799
|
+
const run = this.database.transaction(() => {
|
|
5800
|
+
const current = this.requireRun(input.runId);
|
|
5801
|
+
requireTransition2("Agent Run", current.id, current.state, "retry", ["running"]);
|
|
5802
|
+
const message = this.requireMessage(current.messageId);
|
|
5803
|
+
requireTransition2("Runtime Message", message.id, message.state, "retry", ["leased"]);
|
|
5804
|
+
const { timestamp } = timestampFor();
|
|
5805
|
+
this.database.run(
|
|
5806
|
+
"UPDATE agent_runs SET state = 'queued', lease_id = NULL, updated_at = ? WHERE id = ?",
|
|
5807
|
+
[timestamp, current.id]
|
|
5808
|
+
);
|
|
5809
|
+
this.database.run(
|
|
5810
|
+
"UPDATE runtime_messages SET state = 'queued', updated_at = ? WHERE id = ?",
|
|
5811
|
+
[timestamp, message.id]
|
|
5812
|
+
);
|
|
5813
|
+
this.database.run("DELETE FROM runtime_leases WHERE run_id = ?", [current.id]);
|
|
5814
|
+
this.recordEvent("run_retry_scheduled", {
|
|
5815
|
+
agentId: current.agentId,
|
|
5816
|
+
messageId: message.id,
|
|
5817
|
+
runId: current.id
|
|
5818
|
+
}, { reason: input.reason, attempt: current.attempt });
|
|
5819
|
+
return this.requireRun(current.id);
|
|
5820
|
+
})();
|
|
5821
|
+
return clone3(run);
|
|
5822
|
+
}
|
|
5823
|
+
async exhaustRun(input) {
|
|
5824
|
+
const run = this.database.transaction(() => {
|
|
5825
|
+
const current = this.requireRun(input.runId);
|
|
5826
|
+
requireTransition2("Agent Run", current.id, current.state, "exhaust retries", ["running"]);
|
|
5827
|
+
const message = this.requireMessage(current.messageId);
|
|
5828
|
+
requireTransition2("Runtime Message", message.id, message.state, "dead-letter", ["leased"]);
|
|
5829
|
+
const { timestamp } = timestampFor();
|
|
5830
|
+
this.database.run(
|
|
5831
|
+
"UPDATE agent_runs SET state = 'failed', lease_id = NULL, outcome_json = ?, updated_at = ? WHERE id = ?",
|
|
5832
|
+
[json(input.outcome), timestamp, current.id]
|
|
5833
|
+
);
|
|
5834
|
+
this.database.run(
|
|
5835
|
+
"UPDATE runtime_messages SET state = 'dead_lettered', dead_letter_reason = ?, updated_at = ? WHERE id = ?",
|
|
5836
|
+
[input.reason, timestamp, message.id]
|
|
5837
|
+
);
|
|
5838
|
+
this.database.run("DELETE FROM runtime_leases WHERE run_id = ?", [current.id]);
|
|
5839
|
+
this.recordEvent("run_completed", { agentId: current.agentId, messageId: message.id, runId: current.id }, {
|
|
5840
|
+
state: "failed",
|
|
5841
|
+
outcome: input.outcome
|
|
5842
|
+
});
|
|
5843
|
+
this.recordEvent("message_dead_lettered", { agentId: current.agentId, messageId: message.id }, {
|
|
5844
|
+
reason: input.reason
|
|
5845
|
+
});
|
|
5846
|
+
return this.requireRun(current.id);
|
|
5847
|
+
})();
|
|
5848
|
+
return clone3(run);
|
|
5849
|
+
}
|
|
5850
|
+
async abortRun(input) {
|
|
5851
|
+
const run = this.database.transaction(() => {
|
|
5852
|
+
const current = this.requireRun(input.runId);
|
|
5853
|
+
requireTransition2("Agent Run", current.id, current.state, "abort", ["queued", "running", "suspended"]);
|
|
5854
|
+
const message = this.requireMessage(current.messageId);
|
|
5855
|
+
const { timestamp } = timestampFor();
|
|
5856
|
+
this.database.run(
|
|
5857
|
+
"UPDATE agent_runs SET state = 'cancelled', lease_id = NULL, outcome_json = ?, updated_at = ? WHERE id = ?",
|
|
5858
|
+
[json(input.outcome), timestamp, current.id]
|
|
5859
|
+
);
|
|
5860
|
+
if (message.state === "queued" || message.state === "leased") {
|
|
5861
|
+
this.database.run("UPDATE runtime_messages SET state = 'acknowledged', updated_at = ? WHERE id = ?", [
|
|
5862
|
+
timestamp,
|
|
5863
|
+
message.id
|
|
5864
|
+
]);
|
|
5865
|
+
}
|
|
5866
|
+
this.database.run("DELETE FROM runtime_leases WHERE run_id = ?", [current.id]);
|
|
5867
|
+
this.recordEvent("run_aborted", { agentId: current.agentId, messageId: message.id, runId: current.id }, {
|
|
5868
|
+
outcome: input.outcome
|
|
5869
|
+
});
|
|
5870
|
+
return this.requireRun(current.id);
|
|
5871
|
+
})();
|
|
5872
|
+
return clone3(run);
|
|
5873
|
+
}
|
|
5874
|
+
async recoverExpiredLeases(input) {
|
|
5875
|
+
const { timestamp } = timestampFor(input);
|
|
5876
|
+
const rows = this.database.query(`
|
|
5877
|
+
SELECT r.id
|
|
5878
|
+
FROM agent_runs r
|
|
5879
|
+
JOIN runtime_leases l ON l.id = r.lease_id
|
|
5880
|
+
WHERE r.state = 'running' AND l.expires_at <= ?
|
|
5881
|
+
ORDER BY l.expires_at, r.created_at
|
|
5882
|
+
`).all(timestamp);
|
|
5883
|
+
if (rows.length === 0) return [];
|
|
5884
|
+
const recover = this.database.transaction(() => {
|
|
5885
|
+
const recovered = [];
|
|
5886
|
+
for (const row of rows) {
|
|
5887
|
+
const run = this.requireRun(row.id);
|
|
5888
|
+
if (run.state !== "running") continue;
|
|
5889
|
+
const message = this.requireMessage(run.messageId);
|
|
5890
|
+
this.database.run("UPDATE agent_runs SET state = 'queued', lease_id = NULL, updated_at = ? WHERE id = ?", [
|
|
5891
|
+
timestamp,
|
|
5892
|
+
run.id
|
|
5893
|
+
]);
|
|
5894
|
+
this.database.run("UPDATE runtime_messages SET state = 'queued', updated_at = ? WHERE id = ?", [
|
|
5895
|
+
timestamp,
|
|
5896
|
+
message.id
|
|
5897
|
+
]);
|
|
5898
|
+
this.database.run("DELETE FROM runtime_leases WHERE run_id = ?", [run.id]);
|
|
5899
|
+
this.recordEvent("lease_expired", { agentId: run.agentId, messageId: message.id, runId: run.id });
|
|
5900
|
+
recovered.push(this.requireRun(run.id));
|
|
5901
|
+
}
|
|
5902
|
+
return recovered;
|
|
5903
|
+
})();
|
|
5904
|
+
return clone3(recover);
|
|
5905
|
+
}
|
|
5906
|
+
async recoverLeases() {
|
|
5907
|
+
const { timestamp } = timestampFor();
|
|
5908
|
+
const rows = this.database.query(`
|
|
5909
|
+
SELECT r.id
|
|
5910
|
+
FROM agent_runs r
|
|
5911
|
+
JOIN runtime_leases l ON l.id = r.lease_id
|
|
5912
|
+
WHERE r.state = 'running'
|
|
5913
|
+
ORDER BY l.leased_at, r.created_at
|
|
5914
|
+
`).all();
|
|
5915
|
+
if (rows.length === 0) return [];
|
|
5916
|
+
const recovered = this.database.transaction(() => {
|
|
5917
|
+
const runs = [];
|
|
5918
|
+
for (const row of rows) {
|
|
5919
|
+
const run = this.requireRun(row.id);
|
|
5920
|
+
if (run.state !== "running") continue;
|
|
5921
|
+
const message = this.requireMessage(run.messageId);
|
|
5922
|
+
this.database.run("UPDATE agent_runs SET state = 'queued', lease_id = NULL, updated_at = ? WHERE id = ?", [
|
|
5923
|
+
timestamp,
|
|
5924
|
+
run.id
|
|
5925
|
+
]);
|
|
5926
|
+
this.database.run("UPDATE runtime_messages SET state = 'queued', updated_at = ? WHERE id = ?", [
|
|
5927
|
+
timestamp,
|
|
5928
|
+
message.id
|
|
5929
|
+
]);
|
|
5930
|
+
this.database.run("DELETE FROM runtime_leases WHERE run_id = ?", [run.id]);
|
|
5931
|
+
this.recordEvent("lease_recovered", { agentId: run.agentId, messageId: message.id, runId: run.id });
|
|
5932
|
+
runs.push(this.requireRun(run.id));
|
|
5933
|
+
}
|
|
5934
|
+
return runs;
|
|
5935
|
+
})();
|
|
5936
|
+
return clone3(recovered);
|
|
5937
|
+
}
|
|
5938
|
+
async createToolCall(input) {
|
|
5939
|
+
this.requireAgent(input.agentId);
|
|
5940
|
+
this.requireRun(input.runId);
|
|
5941
|
+
const { timestamp } = timestampFor();
|
|
5942
|
+
const toolCall = {
|
|
5943
|
+
id: createId("call"),
|
|
5944
|
+
agentId: input.agentId,
|
|
5945
|
+
runId: input.runId,
|
|
5946
|
+
name: input.name,
|
|
5947
|
+
args: clone3(input.args),
|
|
5948
|
+
state: "queued",
|
|
5949
|
+
createdAt: timestamp,
|
|
5950
|
+
updatedAt: timestamp
|
|
5951
|
+
};
|
|
5952
|
+
const create = this.database.transaction(() => {
|
|
5953
|
+
this.database.run(
|
|
5954
|
+
"INSERT INTO runtime_tool_calls (id, agent_id, run_id, name, args_json, state, result_json, indeterminate_reason, created_at, updated_at) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)",
|
|
5955
|
+
[toolCall.id, toolCall.agentId, toolCall.runId, toolCall.name, json(toolCall.args), toolCall.state, null, null, toolCall.createdAt, toolCall.updatedAt]
|
|
5956
|
+
);
|
|
5957
|
+
this.recordEvent("tool_call_created", { agentId: toolCall.agentId, runId: toolCall.runId, toolCallId: toolCall.id });
|
|
5958
|
+
});
|
|
5959
|
+
create();
|
|
5960
|
+
return clone3(toolCall);
|
|
5961
|
+
}
|
|
5962
|
+
async startToolCall(toolCallId) {
|
|
5963
|
+
const toolCall = this.database.transaction(() => {
|
|
5964
|
+
const current = this.requireToolCall(toolCallId);
|
|
5965
|
+
requireTransition2("Tool Call", current.id, current.state, "start", ["queued"]);
|
|
5966
|
+
const { timestamp } = timestampFor();
|
|
5967
|
+
this.database.run("UPDATE runtime_tool_calls SET state = ?, updated_at = ? WHERE id = ?", [
|
|
5968
|
+
"running",
|
|
5969
|
+
timestamp,
|
|
5970
|
+
current.id
|
|
5971
|
+
]);
|
|
5972
|
+
this.recordEvent("tool_call_started", { agentId: current.agentId, runId: current.runId, toolCallId: current.id });
|
|
5973
|
+
return this.requireToolCall(current.id);
|
|
5974
|
+
})();
|
|
5975
|
+
return clone3(toolCall);
|
|
5976
|
+
}
|
|
5977
|
+
async completeToolCall(input) {
|
|
5978
|
+
const toolCall = this.database.transaction(() => {
|
|
5979
|
+
const current = this.requireToolCall(input.toolCallId);
|
|
5980
|
+
const state = input.state ?? (input.result.ok ? "completed" : "failed");
|
|
5981
|
+
requireTransition2(
|
|
5982
|
+
"Tool Call",
|
|
5983
|
+
current.id,
|
|
5984
|
+
current.state,
|
|
5985
|
+
"complete",
|
|
5986
|
+
state === "failed" ? ["queued", "running"] : ["running"]
|
|
5987
|
+
);
|
|
5988
|
+
const { timestamp } = timestampFor();
|
|
5989
|
+
this.database.run(
|
|
5990
|
+
"UPDATE runtime_tool_calls SET state = ?, result_json = ?, updated_at = ? WHERE id = ?",
|
|
5991
|
+
[state, json(input.result), timestamp, current.id]
|
|
5992
|
+
);
|
|
5993
|
+
this.recordEvent(state === "failed" ? "tool_call_failed" : "tool_call_completed", { agentId: current.agentId, runId: current.runId, toolCallId: current.id }, {
|
|
5994
|
+
state
|
|
5995
|
+
});
|
|
5996
|
+
return this.requireToolCall(current.id);
|
|
5997
|
+
})();
|
|
5998
|
+
return clone3(toolCall);
|
|
5999
|
+
}
|
|
6000
|
+
async markToolCallIndeterminate(input) {
|
|
6001
|
+
const toolCall = this.database.transaction(() => {
|
|
6002
|
+
const current = this.requireToolCall(input.toolCallId);
|
|
6003
|
+
requireTransition2("Tool Call", current.id, current.state, "mark indeterminate", ["running"]);
|
|
6004
|
+
const { timestamp } = timestampFor();
|
|
6005
|
+
this.database.run(
|
|
6006
|
+
"UPDATE runtime_tool_calls SET state = ?, indeterminate_reason = ?, updated_at = ? WHERE id = ?",
|
|
6007
|
+
["indeterminate", input.reason, timestamp, current.id]
|
|
6008
|
+
);
|
|
6009
|
+
this.recordEvent("tool_call_indeterminate", { agentId: current.agentId, runId: current.runId, toolCallId: current.id }, {
|
|
6010
|
+
reason: input.reason
|
|
6011
|
+
});
|
|
6012
|
+
return this.requireToolCall(current.id);
|
|
6013
|
+
})();
|
|
6014
|
+
return clone3(toolCall);
|
|
6015
|
+
}
|
|
6016
|
+
async getToolCall(toolCallId) {
|
|
6017
|
+
const row = this.database.query("SELECT * FROM runtime_tool_calls WHERE id = ?").get(toolCallId);
|
|
6018
|
+
return row ? this.toolCallFromRow(row) : void 0;
|
|
6019
|
+
}
|
|
6020
|
+
async listEvents(cursor = {}) {
|
|
6021
|
+
const after = cursor.after ? this.database.query("SELECT sequence FROM runtime_events WHERE id = ?").get(cursor.after) : null;
|
|
6022
|
+
const limit = cursor.limit === void 0 ? "" : ` LIMIT ${Math.max(0, Math.floor(cursor.limit))}`;
|
|
6023
|
+
const rows = this.database.query(`SELECT * FROM runtime_events ${after ? "WHERE sequence > ?" : ""} ORDER BY sequence${limit}`).all(...after ? [after.sequence] : []);
|
|
6024
|
+
return rows.map((row) => this.eventFromRow(row));
|
|
6025
|
+
}
|
|
6026
|
+
async recordEvent(input, related = {}, payload) {
|
|
6027
|
+
if (typeof input === "string") {
|
|
6028
|
+
this.recordEventInternal(input, related, payload);
|
|
6029
|
+
const row = this.database.query("SELECT * FROM runtime_events ORDER BY sequence DESC LIMIT 1").get();
|
|
6030
|
+
return clone3(this.eventFromRow(row));
|
|
6031
|
+
}
|
|
6032
|
+
const event = this.database.transaction(() => {
|
|
6033
|
+
this.recordEventInternal(input.kind, input.related ?? {}, input.payload);
|
|
6034
|
+
const row = this.database.query("SELECT * FROM runtime_events ORDER BY sequence DESC LIMIT 1").get();
|
|
6035
|
+
return this.eventFromRow(row);
|
|
6036
|
+
})();
|
|
6037
|
+
return clone3(event);
|
|
6038
|
+
}
|
|
6039
|
+
async getEventCheckpoint(consumerId) {
|
|
6040
|
+
return clone3(this.eventCheckpoint(consumerId));
|
|
6041
|
+
}
|
|
6042
|
+
async acknowledgeEvent(consumerId, eventId) {
|
|
6043
|
+
const checkpoint = this.database.transaction(() => {
|
|
6044
|
+
const event = this.requireEvent(eventId);
|
|
6045
|
+
const current = this.eventCheckpoint(consumerId);
|
|
6046
|
+
if (event.sequence <= current.sequence) return current;
|
|
6047
|
+
if (event.sequence !== current.sequence + 1) {
|
|
6048
|
+
throw new Error(`Runtime Event Consumer Checkpoint cannot advance from sequence ${current.sequence} to ${event.sequence}.`);
|
|
6049
|
+
}
|
|
6050
|
+
const updatedAt = createTimestamp();
|
|
6051
|
+
this.database.run(
|
|
6052
|
+
`INSERT INTO runtime_event_consumers (consumer_id, sequence, event_id, updated_at)
|
|
6053
|
+
VALUES (?, ?, ?, ?)
|
|
6054
|
+
ON CONFLICT(consumer_id) DO UPDATE SET
|
|
6055
|
+
sequence = excluded.sequence,
|
|
6056
|
+
event_id = excluded.event_id,
|
|
6057
|
+
updated_at = excluded.updated_at`,
|
|
6058
|
+
[consumerId, event.sequence, event.id, updatedAt]
|
|
6059
|
+
);
|
|
6060
|
+
return this.eventCheckpoint(consumerId);
|
|
6061
|
+
})();
|
|
6062
|
+
return clone3(checkpoint);
|
|
6063
|
+
}
|
|
6064
|
+
insertMessage(message) {
|
|
6065
|
+
this.database.run(
|
|
6066
|
+
"INSERT INTO runtime_messages (id, agent_id, kind, payload_json, state, attempts, run_id, dead_letter_reason, created_at, updated_at) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)",
|
|
6067
|
+
[message.id, message.agentId, message.kind, json(message.input), message.state, message.attempts, message.runId ?? null, null, message.createdAt, message.updatedAt]
|
|
6068
|
+
);
|
|
6069
|
+
}
|
|
6070
|
+
agentFromRow(row) {
|
|
6071
|
+
return {
|
|
6072
|
+
id: row.id,
|
|
6073
|
+
sessionId: row.session_id,
|
|
6074
|
+
...row.factory_id ? { factoryId: row.factory_id } : {},
|
|
6075
|
+
state: row.state,
|
|
6076
|
+
createdAt: row.created_at,
|
|
6077
|
+
updatedAt: row.updated_at
|
|
6078
|
+
};
|
|
6079
|
+
}
|
|
6080
|
+
messageFromRow(row) {
|
|
6081
|
+
const message = {
|
|
6082
|
+
id: row.id,
|
|
6083
|
+
agentId: row.agent_id,
|
|
6084
|
+
kind: row.kind,
|
|
6085
|
+
input: parse(row.payload_json),
|
|
6086
|
+
state: row.state,
|
|
6087
|
+
attempts: row.attempts,
|
|
6088
|
+
...row.run_id ? { runId: row.run_id } : {},
|
|
6089
|
+
...row.dead_letter_reason ? { deadLetterReason: row.dead_letter_reason } : {},
|
|
6090
|
+
createdAt: row.created_at,
|
|
6091
|
+
updatedAt: row.updated_at
|
|
6092
|
+
};
|
|
6093
|
+
if (row.lease_id && row.lease_run_id && row.worker_id && row.leased_at && row.expires_at) {
|
|
6094
|
+
message.lease = {
|
|
6095
|
+
id: row.lease_id,
|
|
6096
|
+
runId: row.lease_run_id,
|
|
6097
|
+
workerId: row.worker_id,
|
|
6098
|
+
leasedAt: row.leased_at,
|
|
6099
|
+
expiresAt: row.expires_at
|
|
6100
|
+
};
|
|
6101
|
+
}
|
|
6102
|
+
return message;
|
|
6103
|
+
}
|
|
6104
|
+
runFromRow(row) {
|
|
6105
|
+
return {
|
|
6106
|
+
id: row.id,
|
|
6107
|
+
agentId: row.agent_id,
|
|
6108
|
+
messageId: row.message_id,
|
|
6109
|
+
state: row.state,
|
|
6110
|
+
attempt: row.attempt,
|
|
6111
|
+
...row.lease_id ? { leaseId: row.lease_id } : {},
|
|
6112
|
+
...row.outcome_json ? { outcome: parse(row.outcome_json) } : {},
|
|
6113
|
+
...row.suspension_reason ? { suspensionReason: row.suspension_reason } : {},
|
|
6114
|
+
createdAt: row.created_at,
|
|
6115
|
+
updatedAt: row.updated_at
|
|
6116
|
+
};
|
|
6117
|
+
}
|
|
6118
|
+
eventFromRow(row) {
|
|
6119
|
+
return {
|
|
6120
|
+
id: row.id,
|
|
6121
|
+
sequence: row.sequence,
|
|
6122
|
+
kind: row.kind,
|
|
6123
|
+
...row.agent_id ? { agentId: row.agent_id } : {},
|
|
6124
|
+
...row.message_id ? { messageId: row.message_id } : {},
|
|
6125
|
+
...row.run_id ? { runId: row.run_id } : {},
|
|
6126
|
+
...row.tool_call_id ? { toolCallId: row.tool_call_id } : {},
|
|
6127
|
+
...row.payload_json ? { payload: optionalJson(row.payload_json) } : {},
|
|
6128
|
+
createdAt: row.created_at
|
|
6129
|
+
};
|
|
6130
|
+
}
|
|
6131
|
+
toolCallFromRow(row) {
|
|
6132
|
+
return {
|
|
6133
|
+
id: row.id,
|
|
6134
|
+
agentId: row.agent_id,
|
|
6135
|
+
runId: row.run_id,
|
|
6136
|
+
name: row.name,
|
|
6137
|
+
args: parse(row.args_json),
|
|
6138
|
+
state: row.state,
|
|
6139
|
+
...row.result_json ? { result: parse(row.result_json) } : {},
|
|
6140
|
+
...row.indeterminate_reason ? { indeterminateReason: row.indeterminate_reason } : {},
|
|
6141
|
+
createdAt: row.created_at,
|
|
6142
|
+
updatedAt: row.updated_at
|
|
6143
|
+
};
|
|
6144
|
+
}
|
|
6145
|
+
messageRow(messageId) {
|
|
6146
|
+
return this.database.query(`
|
|
6147
|
+
SELECT
|
|
6148
|
+
m.*,
|
|
6149
|
+
l.id AS lease_id,
|
|
6150
|
+
l.run_id AS lease_run_id,
|
|
6151
|
+
l.worker_id,
|
|
6152
|
+
l.leased_at,
|
|
6153
|
+
l.expires_at
|
|
6154
|
+
FROM runtime_messages m
|
|
6155
|
+
LEFT JOIN agent_runs r ON r.id = m.run_id
|
|
6156
|
+
LEFT JOIN runtime_leases l ON l.id = r.lease_id
|
|
6157
|
+
WHERE m.id = ?
|
|
6158
|
+
`).get(messageId);
|
|
6159
|
+
}
|
|
6160
|
+
runRow(runId) {
|
|
6161
|
+
return this.database.query("SELECT * FROM agent_runs WHERE id = ?").get(runId);
|
|
6162
|
+
}
|
|
6163
|
+
requireAgent(agentId) {
|
|
6164
|
+
const row = this.database.query("SELECT * FROM agents WHERE id = ?").get(agentId);
|
|
6165
|
+
if (!row) {
|
|
6166
|
+
throw new Error(`Agent not found: ${agentId}.`);
|
|
6167
|
+
}
|
|
6168
|
+
return this.agentFromRow(row);
|
|
6169
|
+
}
|
|
6170
|
+
requireMessage(messageId) {
|
|
6171
|
+
const row = this.messageRow(messageId);
|
|
6172
|
+
if (!row) {
|
|
6173
|
+
throw new Error(`Runtime Message not found: ${messageId}.`);
|
|
6174
|
+
}
|
|
6175
|
+
return this.messageFromRow(row);
|
|
6176
|
+
}
|
|
6177
|
+
requireRun(runId) {
|
|
6178
|
+
const row = this.runRow(runId);
|
|
6179
|
+
if (!row) {
|
|
6180
|
+
throw new Error(`Agent Run not found: ${runId}.`);
|
|
6181
|
+
}
|
|
6182
|
+
return this.runFromRow(row);
|
|
6183
|
+
}
|
|
6184
|
+
requireToolCall(toolCallId) {
|
|
6185
|
+
const row = this.database.query("SELECT * FROM runtime_tool_calls WHERE id = ?").get(toolCallId);
|
|
6186
|
+
if (!row) {
|
|
6187
|
+
throw new Error(`Tool Call not found: ${toolCallId}.`);
|
|
6188
|
+
}
|
|
6189
|
+
return this.toolCallFromRow(row);
|
|
6190
|
+
}
|
|
6191
|
+
requireEvent(eventId) {
|
|
6192
|
+
const row = this.database.query("SELECT * FROM runtime_events WHERE id = ?").get(eventId);
|
|
6193
|
+
if (!row) {
|
|
6194
|
+
throw new Error(`Runtime Event not found: ${eventId}.`);
|
|
6195
|
+
}
|
|
6196
|
+
return this.eventFromRow(row);
|
|
6197
|
+
}
|
|
6198
|
+
eventCheckpoint(consumerId) {
|
|
6199
|
+
const row = this.database.query(
|
|
6200
|
+
"SELECT consumer_id, sequence, event_id, updated_at FROM runtime_event_consumers WHERE consumer_id = ?"
|
|
6201
|
+
).get(consumerId);
|
|
6202
|
+
if (!row) {
|
|
6203
|
+
return {
|
|
6204
|
+
consumerId,
|
|
6205
|
+
sequence: 0,
|
|
6206
|
+
updatedAt: createTimestamp(/* @__PURE__ */ new Date(0))
|
|
6207
|
+
};
|
|
6208
|
+
}
|
|
6209
|
+
return {
|
|
6210
|
+
consumerId: row.consumer_id,
|
|
6211
|
+
sequence: row.sequence,
|
|
6212
|
+
...row.event_id ? { eventId: row.event_id } : {},
|
|
6213
|
+
updatedAt: row.updated_at
|
|
6214
|
+
};
|
|
6215
|
+
}
|
|
6216
|
+
recordEventInternal(kind, related, payload) {
|
|
6217
|
+
this.database.run(
|
|
6218
|
+
"INSERT INTO runtime_events (id, sequence, kind, agent_id, message_id, run_id, tool_call_id, payload_json, created_at) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)",
|
|
6219
|
+
[
|
|
6220
|
+
createId("evt"),
|
|
6221
|
+
(this.database.query("SELECT COALESCE(MAX(sequence), 0) AS sequence FROM runtime_events").get().sequence ?? 0) + 1,
|
|
6222
|
+
kind,
|
|
6223
|
+
related.agentId ?? null,
|
|
6224
|
+
related.messageId ?? null,
|
|
6225
|
+
related.runId ?? null,
|
|
6226
|
+
related.toolCallId ?? null,
|
|
6227
|
+
payload === void 0 ? null : json(payload),
|
|
6228
|
+
createTimestamp()
|
|
6229
|
+
]
|
|
6230
|
+
);
|
|
6231
|
+
}
|
|
6232
|
+
};
|
|
4489
6233
|
export {
|
|
4490
6234
|
Agent,
|
|
4491
|
-
|
|
4492
|
-
|
|
4493
|
-
|
|
4494
|
-
|
|
4495
|
-
|
|
4496
|
-
|
|
4497
|
-
appendUserTurn,
|
|
4498
|
-
buildModelRequest,
|
|
4499
|
-
buildSystemPrompt,
|
|
4500
|
-
conversationMessages,
|
|
6235
|
+
AgentRun,
|
|
6236
|
+
AgentRuntime,
|
|
6237
|
+
InMemoryRuntimeStateStore,
|
|
6238
|
+
InMemorySessionProvider,
|
|
6239
|
+
LocalJsonlSessionProvider,
|
|
6240
|
+
SqliteRuntimeStateStore,
|
|
4501
6241
|
createCoreTools,
|
|
4502
|
-
|
|
4503
|
-
createEventBus,
|
|
4504
|
-
createExtension,
|
|
4505
|
-
createExtensionAPI,
|
|
4506
|
-
createExtensionRunner,
|
|
4507
|
-
createExtensionRuntime,
|
|
4508
|
-
createId,
|
|
4509
|
-
createMessage,
|
|
4510
|
-
createSession,
|
|
4511
|
-
createSourceInfo,
|
|
4512
|
-
createTimestamp,
|
|
4513
|
-
getGlobalHooks,
|
|
4514
|
-
interpolateEnvVars,
|
|
4515
|
-
latestUserInput,
|
|
4516
|
-
loadConfigFile,
|
|
4517
|
-
loadPhase,
|
|
4518
|
-
loadPhases,
|
|
4519
|
-
messageContentText,
|
|
4520
|
-
parseModelRef,
|
|
4521
|
-
registerBuiltInApiProviders,
|
|
4522
|
-
registerConfigModels,
|
|
4523
|
-
reloadPhases,
|
|
4524
|
-
resetGlobalHooks,
|
|
4525
|
-
resolveDefaultModel,
|
|
4526
|
-
resolveInWorkspace,
|
|
4527
|
-
resolveWorkspacePaths,
|
|
4528
|
-
serializeSkills
|
|
6242
|
+
createMessage
|
|
4529
6243
|
};
|