@rowan-agent/agent 0.5.6 → 0.6.1
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 +338 -326
- package/dist/index.d.ts +758 -914
- package/dist/index.js +2551 -557
- 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) {
|
|
@@ -694,10 +521,10 @@ ${indent}</${tag}>`;
|
|
|
694
521
|
return `${indent}<${tag}>${escapeXml(String(val))}</${tag}>`;
|
|
695
522
|
}).join("\n");
|
|
696
523
|
}
|
|
697
|
-
function buildPhaseDirectiveMessage(phase, output
|
|
524
|
+
function buildPhaseDirectiveMessage(phase, output) {
|
|
698
525
|
const parts = [];
|
|
699
|
-
parts.push(`<
|
|
700
|
-
parts.push(
|
|
526
|
+
parts.push(`<phase_content name="${escapeXml(phase.name)}">`);
|
|
527
|
+
parts.push(phase.content);
|
|
701
528
|
if (output.results && output.results.length > 0) {
|
|
702
529
|
parts.push(` <prev_phase_outputs>`);
|
|
703
530
|
if (output.instruction) {
|
|
@@ -712,20 +539,15 @@ function buildPhaseDirectiveMessage(phase, output, toolUseId) {
|
|
|
712
539
|
}
|
|
713
540
|
parts.push(` </prev_phase_outputs>`);
|
|
714
541
|
}
|
|
715
|
-
parts.push(`</
|
|
716
|
-
return
|
|
717
|
-
type: "tool_result",
|
|
718
|
-
toolUseId,
|
|
719
|
-
content: parts.join("\n"),
|
|
720
|
-
isError: false
|
|
721
|
-
}];
|
|
542
|
+
parts.push(`</phase_content>`);
|
|
543
|
+
return parts.join("\n");
|
|
722
544
|
}
|
|
723
545
|
|
|
724
546
|
// src/harness/phases/loader.ts
|
|
725
547
|
var PHASE_MARKER = "PHASE.md";
|
|
726
548
|
function resolvePhasePath(input) {
|
|
727
549
|
const resolved = resolve(input);
|
|
728
|
-
return
|
|
550
|
+
return existsSync(resolved) && statSync(resolved).isDirectory() ? join(resolved, PHASE_MARKER) : resolved;
|
|
729
551
|
}
|
|
730
552
|
async function loadPhase(targetPath) {
|
|
731
553
|
const resolved = resolvePhasePath(targetPath);
|
|
@@ -772,18 +594,15 @@ function readPhaseContent(phase) {
|
|
|
772
594
|
baseDir: phase.baseDir
|
|
773
595
|
});
|
|
774
596
|
}
|
|
775
|
-
function resolvePhaseLoadTarget(targetPath) {
|
|
776
|
-
return typeof targetPath === "string" ? targetPath : join2(targetPath.rowanDir, "phases");
|
|
777
|
-
}
|
|
778
597
|
async function loadPhases(targetPath) {
|
|
779
598
|
const phases = /* @__PURE__ */ new Map();
|
|
780
|
-
const phasesDir = resolve(
|
|
781
|
-
if (
|
|
599
|
+
const phasesDir = resolve(targetPath);
|
|
600
|
+
if (existsSync(phasesDir) && statSync(phasesDir).isFile()) {
|
|
782
601
|
const phase = await loadPhase(phasesDir);
|
|
783
602
|
phases.set(phase.id, phase);
|
|
784
603
|
return { phases, entryPhaseId: null };
|
|
785
604
|
}
|
|
786
|
-
if (
|
|
605
|
+
if (existsSync(join(phasesDir, PHASE_MARKER))) {
|
|
787
606
|
const phase = await loadPhase(phasesDir);
|
|
788
607
|
phases.set(phase.id, phase);
|
|
789
608
|
return { phases, entryPhaseId: null };
|
|
@@ -791,8 +610,8 @@ async function loadPhases(targetPath) {
|
|
|
791
610
|
const entries = await readdir(phasesDir, { withFileTypes: true });
|
|
792
611
|
for (const entry of [...entries].sort((a, b) => a.name.localeCompare(b.name))) {
|
|
793
612
|
if (!entry.isDirectory()) continue;
|
|
794
|
-
const phaseFile =
|
|
795
|
-
if (!
|
|
613
|
+
const phaseFile = join(phasesDir, entry.name, PHASE_MARKER);
|
|
614
|
+
if (!existsSync(phaseFile)) continue;
|
|
796
615
|
try {
|
|
797
616
|
const phase = await loadPhase(phaseFile);
|
|
798
617
|
phases.set(phase.id, phase);
|
|
@@ -805,8 +624,8 @@ async function loadPhases(targetPath) {
|
|
|
805
624
|
async function discoverPhaseCode(baseDir) {
|
|
806
625
|
const candidates = ["index.ts", "index.js"];
|
|
807
626
|
for (const candidate of candidates) {
|
|
808
|
-
const fullPath =
|
|
809
|
-
if (
|
|
627
|
+
const fullPath = join(baseDir, candidate);
|
|
628
|
+
if (existsSync(fullPath)) {
|
|
810
629
|
return fullPath;
|
|
811
630
|
}
|
|
812
631
|
}
|
|
@@ -815,7 +634,9 @@ async function discoverPhaseCode(baseDir) {
|
|
|
815
634
|
async function loadPhaseCode(codePath) {
|
|
816
635
|
const { createJiti: createJiti2 } = await import("jiti");
|
|
817
636
|
const jiti = createJiti2(import.meta.url, {
|
|
818
|
-
|
|
637
|
+
fsCache: false,
|
|
638
|
+
moduleCache: false,
|
|
639
|
+
tryNative: false
|
|
819
640
|
});
|
|
820
641
|
const mod = await jiti.import(codePath, { default: true });
|
|
821
642
|
const fn = typeof mod === "function" ? mod : mod?.default;
|
|
@@ -827,19 +648,6 @@ async function loadPhaseCode(codePath) {
|
|
|
827
648
|
}
|
|
828
649
|
throw new Error(`Phase code at "${codePath}" must export a default function or a run() function.`);
|
|
829
650
|
}
|
|
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
651
|
|
|
844
652
|
// src/harness/phases/default.ts
|
|
845
653
|
var DEFAULT_PHASE_ID = "default";
|
|
@@ -855,119 +663,16 @@ function createDefaultPhase() {
|
|
|
855
663
|
}
|
|
856
664
|
|
|
857
665
|
// src/harness/tools/index.ts
|
|
858
|
-
import { mkdir, readFile as
|
|
859
|
-
import { basename as basename2, dirname as
|
|
666
|
+
import { mkdir, readFile as readFile2, stat, writeFile } from "fs/promises";
|
|
667
|
+
import { basename as basename2, dirname as dirname3, isAbsolute as isAbsolute2, relative as relative2, resolve as resolve3, sep as sep2 } from "path";
|
|
860
668
|
import Type2 from "typebox";
|
|
861
669
|
import Schema from "typebox/schema";
|
|
862
670
|
|
|
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
|
-
}
|
|
671
|
+
// src/harness/path.ts
|
|
672
|
+
import { isAbsolute, relative, resolve as resolve2, sep } from "path";
|
|
946
673
|
function normalizeRelativePath(path) {
|
|
947
674
|
return path.split(sep).join("/");
|
|
948
675
|
}
|
|
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
676
|
|
|
972
677
|
// src/harness/tools/route-tool.ts
|
|
973
678
|
import Type from "typebox";
|
|
@@ -1310,7 +1015,7 @@ function createReadTool(context) {
|
|
|
1310
1015
|
error: `Not a file: ${resolved.relativePath}`
|
|
1311
1016
|
});
|
|
1312
1017
|
}
|
|
1313
|
-
const bytes = await
|
|
1018
|
+
const bytes = await readFile2(resolved.absolutePath);
|
|
1314
1019
|
const sliced = bytes.subarray(0, maxBytes);
|
|
1315
1020
|
const text = new TextDecoder().decode(sliced);
|
|
1316
1021
|
const resourceType = parsed.type ?? detectResourceType(resolved.absolutePath);
|
|
@@ -1320,7 +1025,7 @@ function createReadTool(context) {
|
|
|
1320
1025
|
const { frontmatter } = parseFrontmatter(text);
|
|
1321
1026
|
const marker = resourceType === "skill" ? "SKILL.md" : "PHASE.md";
|
|
1322
1027
|
name = frontmatter.name ?? inferResourceName(resolved.absolutePath, marker);
|
|
1323
|
-
baseDir =
|
|
1028
|
+
baseDir = dirname3(resolved.absolutePath);
|
|
1324
1029
|
} else {
|
|
1325
1030
|
name = inferResourceName(resolved.absolutePath, basename2(resolved.absolutePath));
|
|
1326
1031
|
}
|
|
@@ -1351,7 +1056,7 @@ function createWriteTool(context) {
|
|
|
1351
1056
|
async execute(args, toolContext) {
|
|
1352
1057
|
const parsed = WriteArgsValidator.Parse(args);
|
|
1353
1058
|
const resolved = resolveCoreToolPath(context, parsed.path);
|
|
1354
|
-
await mkdir(
|
|
1059
|
+
await mkdir(dirname3(resolved.absolutePath), { recursive: true });
|
|
1355
1060
|
await writeFile(resolved.absolutePath, parsed.content, "utf8");
|
|
1356
1061
|
return toolResult({
|
|
1357
1062
|
context: toolContext,
|
|
@@ -1388,7 +1093,7 @@ function createEditTool(context) {
|
|
|
1388
1093
|
});
|
|
1389
1094
|
}
|
|
1390
1095
|
const resolved = resolveCoreToolPath(context, parsed.path);
|
|
1391
|
-
const current = await
|
|
1096
|
+
const current = await readFile2(resolved.absolutePath, "utf8");
|
|
1392
1097
|
if (!current.includes(parsed.oldText)) {
|
|
1393
1098
|
return toolResult({
|
|
1394
1099
|
context: toolContext,
|
|
@@ -1576,15 +1281,6 @@ function serializeSkills(skills) {
|
|
|
1576
1281
|
disableModelInvocation: skill.disableModelInvocation
|
|
1577
1282
|
}));
|
|
1578
1283
|
}
|
|
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
1284
|
function conversationMessages(messages) {
|
|
1589
1285
|
return messages.flatMap((message) => {
|
|
1590
1286
|
if (message.role === "user") {
|
|
@@ -1665,11 +1361,17 @@ function compactMessages(messages, options = {}) {
|
|
|
1665
1361
|
);
|
|
1666
1362
|
const recentStart = Math.max(messages.length - keepRecent, firstUserIdx + 1);
|
|
1667
1363
|
const oldMessages = messages.slice(firstUserIdx >= 0 ? firstUserIdx + 1 : 0, recentStart);
|
|
1668
|
-
|
|
1364
|
+
const protectedPhaseMessages = oldMessages.filter(
|
|
1365
|
+
(message) => message.metadata?.kind === "phase_prompt"
|
|
1366
|
+
);
|
|
1367
|
+
const summarizableMessages = oldMessages.filter(
|
|
1368
|
+
(message) => message.metadata?.kind !== "phase_prompt"
|
|
1369
|
+
);
|
|
1370
|
+
if (summarizableMessages.length < minCompact) {
|
|
1669
1371
|
return { compacted: false, messages };
|
|
1670
1372
|
}
|
|
1671
1373
|
const recentMessages = messages.slice(recentStart);
|
|
1672
|
-
const summary = buildSummary(
|
|
1374
|
+
const summary = buildSummary(summarizableMessages);
|
|
1673
1375
|
const result = [];
|
|
1674
1376
|
if (firstUserIdx >= 0) {
|
|
1675
1377
|
result.push(messages[firstUserIdx]);
|
|
@@ -1679,14 +1381,15 @@ function compactMessages(messages, options = {}) {
|
|
|
1679
1381
|
|
|
1680
1382
|
${summary}`, {
|
|
1681
1383
|
type: "compaction_summary",
|
|
1682
|
-
compactedCount:
|
|
1384
|
+
compactedCount: summarizableMessages.length
|
|
1683
1385
|
})
|
|
1684
1386
|
);
|
|
1387
|
+
result.push(...protectedPhaseMessages);
|
|
1685
1388
|
result.push(...recentMessages);
|
|
1686
1389
|
return {
|
|
1687
1390
|
compacted: true,
|
|
1688
1391
|
messages: result,
|
|
1689
|
-
summarizedCount:
|
|
1392
|
+
summarizedCount: summarizableMessages.length,
|
|
1690
1393
|
summary
|
|
1691
1394
|
};
|
|
1692
1395
|
}
|
|
@@ -1716,17 +1419,20 @@ function applyFirstDecision(route, output) {
|
|
|
1716
1419
|
if (first.reason) output.routeReason = first.reason;
|
|
1717
1420
|
if (first.payload !== void 0) output.payload = normalizePayload(first.payload);
|
|
1718
1421
|
}
|
|
1719
|
-
function injectPhaseContent(phase, output,
|
|
1422
|
+
function injectPhaseContent(phase, output, messages, mirror) {
|
|
1720
1423
|
try {
|
|
1721
1424
|
const phaseContent = phase.filePath ? readPhaseContent(phase) : phase.content ?? phase.description ?? "";
|
|
1722
1425
|
const content = buildPhaseDirectiveMessage(
|
|
1723
1426
|
{ name: phase.id, content: phaseContent },
|
|
1724
|
-
output
|
|
1725
|
-
`phase_${phase.id}`
|
|
1427
|
+
output
|
|
1726
1428
|
);
|
|
1727
|
-
const
|
|
1728
|
-
|
|
1729
|
-
|
|
1429
|
+
const message = createMessage("user", content, {
|
|
1430
|
+
kind: "phase_prompt",
|
|
1431
|
+
phase: phase.id
|
|
1432
|
+
});
|
|
1433
|
+
messages.push(message);
|
|
1434
|
+
if (mirror && mirror !== messages) mirror.push(message);
|
|
1435
|
+
return message.id;
|
|
1730
1436
|
} catch {
|
|
1731
1437
|
return void 0;
|
|
1732
1438
|
}
|
|
@@ -1889,12 +1595,16 @@ async function startPhaseLoop(config, state) {
|
|
|
1889
1595
|
return runPhaseLoop(config, state, registry);
|
|
1890
1596
|
}
|
|
1891
1597
|
async function runPhaseLoop(config, state, registry) {
|
|
1892
|
-
|
|
1893
|
-
let
|
|
1894
|
-
let
|
|
1895
|
-
|
|
1896
|
-
|
|
1897
|
-
|
|
1598
|
+
const resumingSuspendedRun = state.status === "suspended" && Boolean(state.currentPhase);
|
|
1599
|
+
let currentPhaseId = resumingSuspendedRun ? state.currentPhase : registry.entryPhaseId;
|
|
1600
|
+
let isContinuing = resumingSuspendedRun ? state.continuation?.isContinuing ?? true : false;
|
|
1601
|
+
if (resumingSuspendedRun) {
|
|
1602
|
+
state.status = "running";
|
|
1603
|
+
}
|
|
1604
|
+
let previousPayload = resumingSuspendedRun ? state.continuation?.previousPayload : void 0;
|
|
1605
|
+
let previousPhaseMsgId = resumingSuspendedRun ? state.continuation?.previousPhaseMessageId : void 0;
|
|
1606
|
+
let previousResults = resumingSuspendedRun ? state.continuation?.previousResults?.map((result) => ({ ...result })) ?? [] : [];
|
|
1607
|
+
let pendingInstruction = resumingSuspendedRun ? state.continuation?.pendingInstruction : void 0;
|
|
1898
1608
|
while (currentPhaseId) {
|
|
1899
1609
|
const availablePhases = [];
|
|
1900
1610
|
for (const [, phase2] of registry.phases) {
|
|
@@ -1902,6 +1612,8 @@ async function runPhaseLoop(config, state, registry) {
|
|
|
1902
1612
|
}
|
|
1903
1613
|
const abortResult = LoopGuard.checkAbort(config.signal);
|
|
1904
1614
|
if (abortResult.stopReason !== "none") {
|
|
1615
|
+
removePhaseMessage(config.context.messages, previousPhaseMsgId);
|
|
1616
|
+
previousPhaseMsgId = void 0;
|
|
1905
1617
|
return completeRun(config, state, createOutcome.aborted());
|
|
1906
1618
|
}
|
|
1907
1619
|
state.metrics.iterations++;
|
|
@@ -1939,6 +1651,10 @@ async function runPhaseLoop(config, state, registry) {
|
|
|
1939
1651
|
messages: messageManager.visible(),
|
|
1940
1652
|
tools: phaseTools,
|
|
1941
1653
|
skills: phaseSkills,
|
|
1654
|
+
invocation: {
|
|
1655
|
+
mode: "serial",
|
|
1656
|
+
instanceId: currentPhaseId
|
|
1657
|
+
},
|
|
1942
1658
|
state: {
|
|
1943
1659
|
current: currentPhaseId,
|
|
1944
1660
|
available: Array.from(registry.phases.keys()),
|
|
@@ -1955,10 +1671,14 @@ async function runPhaseLoop(config, state, registry) {
|
|
|
1955
1671
|
const extBefore = await config.beforePhase(currentPhaseId, phaseContext);
|
|
1956
1672
|
if (extBefore.abort) {
|
|
1957
1673
|
config.emit?.({ type: "phase_end", phase: currentPhaseId, ts: createTimestamp() });
|
|
1674
|
+
removePhaseMessage(config.context.messages, previousPhaseMsgId);
|
|
1675
|
+
previousPhaseMsgId = void 0;
|
|
1958
1676
|
return completeRun(config, state, extBefore.abort);
|
|
1959
1677
|
}
|
|
1960
1678
|
if (extBefore.skip) {
|
|
1961
1679
|
config.emit?.({ type: "phase_end", phase: currentPhaseId, ts: createTimestamp() });
|
|
1680
|
+
removePhaseMessage(config.context.messages, previousPhaseMsgId);
|
|
1681
|
+
previousPhaseMsgId = void 0;
|
|
1962
1682
|
if (extBefore.skip.route === "stop") {
|
|
1963
1683
|
return completeRun(config, state, {
|
|
1964
1684
|
id: "skip",
|
|
@@ -1972,21 +1692,26 @@ async function runPhaseLoop(config, state, registry) {
|
|
|
1972
1692
|
phaseContext = extBefore.input;
|
|
1973
1693
|
}
|
|
1974
1694
|
}
|
|
1975
|
-
|
|
1976
|
-
|
|
1977
|
-
|
|
1978
|
-
|
|
1695
|
+
const phaseMessageIsPresent = previousPhaseMsgId !== void 0 && config.context.messages.some((message) => message.id === previousPhaseMsgId);
|
|
1696
|
+
if (enteringNewPhase || !phaseMessageIsPresent) {
|
|
1697
|
+
removePhaseMessage(config.context.messages, previousPhaseMsgId);
|
|
1698
|
+
removePhaseMessage(phaseContext.messages, previousPhaseMsgId);
|
|
1699
|
+
previousPhaseMsgId = void 0;
|
|
1700
|
+
previousPhaseMsgId = injectPhaseContent(
|
|
1701
|
+
phase,
|
|
1702
|
+
{ results: previousResults, instruction: pendingInstruction },
|
|
1703
|
+
config.context.messages,
|
|
1704
|
+
phaseContext.messages
|
|
1705
|
+
);
|
|
1979
1706
|
previousResults = [];
|
|
1980
1707
|
pendingInstruction = void 0;
|
|
1981
1708
|
}
|
|
1982
1709
|
const runtime = { phase, config, state, execution, messageManager, registry, context: phaseContext };
|
|
1983
1710
|
let output = await executePhase(runtime);
|
|
1984
|
-
let routeToolCalled = false;
|
|
1985
1711
|
let routeDecision;
|
|
1986
1712
|
if (output.toolCalls && output.toolCalls.length > 0) {
|
|
1987
1713
|
routeDecision = extractRouteCall(output.toolCalls);
|
|
1988
1714
|
if (routeDecision) {
|
|
1989
|
-
routeToolCalled = true;
|
|
1990
1715
|
applyFirstDecision(routeDecision, output);
|
|
1991
1716
|
}
|
|
1992
1717
|
}
|
|
@@ -1994,6 +1719,8 @@ async function runPhaseLoop(config, state, registry) {
|
|
|
1994
1719
|
const extAfter = await config.afterPhase(currentPhaseId, output);
|
|
1995
1720
|
if (extAfter.abort) {
|
|
1996
1721
|
config.emit?.({ type: "phase_end", phase: currentPhaseId, ts: createTimestamp() });
|
|
1722
|
+
removePhaseMessage(config.context.messages, previousPhaseMsgId);
|
|
1723
|
+
previousPhaseMsgId = void 0;
|
|
1997
1724
|
return completeRun(config, state, extAfter.abort);
|
|
1998
1725
|
}
|
|
1999
1726
|
if (extAfter.retry && (phase.run || phase.factory)) {
|
|
@@ -2016,8 +1743,27 @@ async function runPhaseLoop(config, state, registry) {
|
|
|
2016
1743
|
previousPhaseMsgId = void 0;
|
|
2017
1744
|
return completeRun(config, state, createOutcome.aborted());
|
|
2018
1745
|
}
|
|
2019
|
-
|
|
2020
|
-
const
|
|
1746
|
+
const requestedAt = createTimestamp();
|
|
1747
|
+
const inputRequest = {
|
|
1748
|
+
phase: currentPhaseId,
|
|
1749
|
+
prompt: output.message,
|
|
1750
|
+
requestedAt
|
|
1751
|
+
};
|
|
1752
|
+
config.emit?.({
|
|
1753
|
+
type: "user_prompt_requested",
|
|
1754
|
+
phase: currentPhaseId,
|
|
1755
|
+
ts: requestedAt
|
|
1756
|
+
});
|
|
1757
|
+
state.status = "suspended";
|
|
1758
|
+
state.continuation = {
|
|
1759
|
+
isContinuing,
|
|
1760
|
+
previousPayload,
|
|
1761
|
+
previousResults: previousResults.map((result) => ({ ...result })),
|
|
1762
|
+
pendingInstruction,
|
|
1763
|
+
previousPhaseMessageId: previousPhaseMsgId
|
|
1764
|
+
};
|
|
1765
|
+
const userMessages = await config.waitForInput(state, inputRequest);
|
|
1766
|
+
state.status = "running";
|
|
2021
1767
|
const abortResult2 = LoopGuard.checkAbort(config.signal);
|
|
2022
1768
|
if (abortResult2.stopReason !== "none") {
|
|
2023
1769
|
config.emit?.({ type: "phase_end", phase: currentPhaseId, ts: createTimestamp() });
|
|
@@ -2028,13 +1774,12 @@ async function runPhaseLoop(config, state, registry) {
|
|
|
2028
1774
|
for (const message of userMessages) {
|
|
2029
1775
|
config.context.messages.push(message);
|
|
2030
1776
|
}
|
|
2031
|
-
removePhaseMessage(config.context.messages, previousPhaseMsgId);
|
|
2032
|
-
previousPhaseMsgId = void 0;
|
|
2033
1777
|
isContinuing = true;
|
|
2034
1778
|
continue;
|
|
2035
1779
|
}
|
|
2036
1780
|
config.emit?.({ type: "phase_end", phase: currentPhaseId, ts: createTimestamp() });
|
|
2037
1781
|
removePhaseMessage(config.context.messages, previousPhaseMsgId);
|
|
1782
|
+
previousPhaseMsgId = void 0;
|
|
2038
1783
|
return completeRun(config, state, createOutcome.default(output, config.context.messages));
|
|
2039
1784
|
}
|
|
2040
1785
|
if (output.route === "continue") {
|
|
@@ -2046,6 +1791,8 @@ async function runPhaseLoop(config, state, registry) {
|
|
|
2046
1791
|
const contextSnapshot = snapshotMessages(config.context.messages);
|
|
2047
1792
|
const parallelTasks = /* @__PURE__ */ new Map();
|
|
2048
1793
|
const instanceIds = buildInstanceIds(routeDecision.decision.map((t) => t.phase));
|
|
1794
|
+
const groupId = createId("phase-group");
|
|
1795
|
+
const count = routeDecision.decision.length;
|
|
2049
1796
|
for (let i = 0; i < routeDecision.decision.length; i++) {
|
|
2050
1797
|
const target = routeDecision.decision[i];
|
|
2051
1798
|
const pt = registry.phases.get(target.phase);
|
|
@@ -2053,7 +1800,20 @@ async function runPhaseLoop(config, state, registry) {
|
|
|
2053
1800
|
const instanceId = instanceIds[i];
|
|
2054
1801
|
const context = pt.isolated ? [] : contextSnapshot;
|
|
2055
1802
|
const payload = target.payload !== void 0 ? normalizePayload(target.payload) : void 0;
|
|
2056
|
-
const promise = executeParallelPhase(
|
|
1803
|
+
const promise = executeParallelPhase(
|
|
1804
|
+
config,
|
|
1805
|
+
state,
|
|
1806
|
+
registry,
|
|
1807
|
+
pt,
|
|
1808
|
+
payload,
|
|
1809
|
+
context,
|
|
1810
|
+
availablePhases,
|
|
1811
|
+
instanceId,
|
|
1812
|
+
groupId,
|
|
1813
|
+
i,
|
|
1814
|
+
count,
|
|
1815
|
+
currentPhaseId
|
|
1816
|
+
);
|
|
2057
1817
|
parallelTasks.set(instanceId, { promise, phaseId: target.phase });
|
|
2058
1818
|
}
|
|
2059
1819
|
const successfulResults = await waitForBackgroundTasks(parallelTasks);
|
|
@@ -2061,6 +1821,8 @@ async function runPhaseLoop(config, state, registry) {
|
|
|
2061
1821
|
pendingInstruction = routeDecision.instruction;
|
|
2062
1822
|
const entryPhaseId = phase.target ?? registry.entryPhaseId;
|
|
2063
1823
|
if (entryPhaseId === "stop") {
|
|
1824
|
+
removePhaseMessage(config.context.messages, previousPhaseMsgId);
|
|
1825
|
+
previousPhaseMsgId = void 0;
|
|
2064
1826
|
return completeRun(config, state, createTerminalOutcome(phase, output, config.context.messages));
|
|
2065
1827
|
}
|
|
2066
1828
|
currentPhaseId = entryPhaseId;
|
|
@@ -2076,12 +1838,14 @@ async function runPhaseLoop(config, state, registry) {
|
|
|
2076
1838
|
nextRoute = "stop";
|
|
2077
1839
|
}
|
|
2078
1840
|
if (nextRoute === "stop") {
|
|
2079
|
-
|
|
1841
|
+
removePhaseMessage(config.context.messages, previousPhaseMsgId);
|
|
1842
|
+
previousPhaseMsgId = void 0;
|
|
2080
1843
|
return completeRun(config, state, createTerminalOutcome(phase, output, config.context.messages));
|
|
2081
1844
|
}
|
|
2082
1845
|
const targetPhaseId = nextRoute;
|
|
2083
1846
|
if (!registry.phases.has(targetPhaseId)) {
|
|
2084
1847
|
removePhaseMessage(config.context.messages, previousPhaseMsgId);
|
|
1848
|
+
previousPhaseMsgId = void 0;
|
|
2085
1849
|
return completeRun(config, state, createOutcome.phaseNotFound(output));
|
|
2086
1850
|
}
|
|
2087
1851
|
state.metrics.phaseTransitions.push({
|
|
@@ -2298,17 +2062,25 @@ function createPhaseExecution(config, state, allTools, phase, messageManager, to
|
|
|
2298
2062
|
}
|
|
2299
2063
|
};
|
|
2300
2064
|
}
|
|
2301
|
-
async function executeParallelPhase(config, state, registry, phase, payload, context, availablePhases,
|
|
2065
|
+
async function executeParallelPhase(config, state, registry, phase, payload, context, availablePhases, instanceId, groupId, index, count, sourcePhaseId) {
|
|
2302
2066
|
const messages = [...context];
|
|
2303
2067
|
const allTools = buildToolsWithRouting(config, availablePhases);
|
|
2304
2068
|
const phaseTools = phase.tools ? allTools.filter((t) => t.name === PhaseRouteTool || phase.tools.includes(t.name)) : allTools;
|
|
2305
2069
|
const phaseSkills = phase.skills ? config.context.skills.filter((s) => phase.skills.includes(s.name)) : config.context.skills;
|
|
2306
|
-
const systemPrompt =
|
|
2070
|
+
const systemPrompt = config.context.systemPrompt;
|
|
2307
2071
|
const phaseContext = {
|
|
2308
2072
|
systemPrompt,
|
|
2309
2073
|
messages,
|
|
2310
2074
|
tools: phaseTools,
|
|
2311
2075
|
skills: phaseSkills,
|
|
2076
|
+
invocation: {
|
|
2077
|
+
mode: "parallel",
|
|
2078
|
+
instanceId,
|
|
2079
|
+
groupId,
|
|
2080
|
+
index,
|
|
2081
|
+
count,
|
|
2082
|
+
sourcePhaseId
|
|
2083
|
+
},
|
|
2312
2084
|
state: {
|
|
2313
2085
|
current: phase.id,
|
|
2314
2086
|
available: Array.from(registry.phases.keys()),
|
|
@@ -2317,10 +2089,10 @@ async function executeParallelPhase(config, state, registry, phase, payload, con
|
|
|
2317
2089
|
}
|
|
2318
2090
|
};
|
|
2319
2091
|
const messageManager = createMessageManager({ messages }, config.emit, config.onMessage);
|
|
2320
|
-
const phaseMsgId =
|
|
2092
|
+
const phaseMsgId = injectPhaseContent(
|
|
2321
2093
|
phase,
|
|
2322
|
-
{ results: payload !== void 0 ? [{ name:
|
|
2323
|
-
|
|
2094
|
+
{ results: payload !== void 0 ? [{ name: sourcePhaseId, output: payload }] : [] },
|
|
2095
|
+
messages
|
|
2324
2096
|
);
|
|
2325
2097
|
const toolExecutionManager = createToolExecutionManager(config.emit);
|
|
2326
2098
|
const execution = createPhaseExecution(config, state, phaseTools, phase, messageManager, toolExecutionManager, registry);
|
|
@@ -2329,7 +2101,7 @@ async function executeParallelPhase(config, state, registry, phase, payload, con
|
|
|
2329
2101
|
if (phaseMsgId) removePhaseMessage(messages, phaseMsgId);
|
|
2330
2102
|
const decision = output.toolCalls ? extractRouteCall(output.toolCalls) : void 0;
|
|
2331
2103
|
const resultPayload = decision?.decision[0]?.payload !== void 0 ? normalizePayload(decision.decision[0].payload) : output.payload;
|
|
2332
|
-
return { instanceId
|
|
2104
|
+
return { instanceId, phaseId: phase.id, payload: resultPayload, content: output.message };
|
|
2333
2105
|
}
|
|
2334
2106
|
async function waitForBackgroundTasks(backgroundTasks) {
|
|
2335
2107
|
const entries = Array.from(backgroundTasks.entries());
|
|
@@ -2398,13 +2170,13 @@ async function completeRun2(config, state, outcome) {
|
|
|
2398
2170
|
}
|
|
2399
2171
|
|
|
2400
2172
|
// src/harness/skills.ts
|
|
2401
|
-
import { existsSync as
|
|
2173
|
+
import { existsSync as existsSync2, statSync as statSync2 } from "fs";
|
|
2402
2174
|
import { readdir as readdir2 } from "fs/promises";
|
|
2403
|
-
import { dirname as
|
|
2175
|
+
import { dirname as dirname4, join as join2, resolve as resolve4 } from "path";
|
|
2404
2176
|
var SKILL_MARKER = "SKILL.md";
|
|
2405
2177
|
function resolveSkillPath(input) {
|
|
2406
2178
|
const resolved = resolve4(input);
|
|
2407
|
-
return
|
|
2179
|
+
return existsSync2(resolved) && statSync2(resolved).isDirectory() ? join2(resolved, SKILL_MARKER) : resolved;
|
|
2408
2180
|
}
|
|
2409
2181
|
function readSkillContent(skill) {
|
|
2410
2182
|
return formatResourceOutput({
|
|
@@ -2422,25 +2194,25 @@ async function loadSkill(path) {
|
|
|
2422
2194
|
name: frontmatter.name ?? inferResourceName(resolved, SKILL_MARKER),
|
|
2423
2195
|
description: frontmatter.description ?? "",
|
|
2424
2196
|
filePath: resolved,
|
|
2425
|
-
baseDir:
|
|
2197
|
+
baseDir: dirname4(resolved),
|
|
2426
2198
|
content: body,
|
|
2427
2199
|
disableModelInvocation: frontmatter["disable-model-invocation"] === true
|
|
2428
2200
|
};
|
|
2429
2201
|
}
|
|
2430
2202
|
async function loadSkills(targetPath) {
|
|
2431
2203
|
const skillsDir = resolve4(targetPath);
|
|
2432
|
-
if (
|
|
2204
|
+
if (existsSync2(skillsDir) && statSync2(skillsDir).isFile()) {
|
|
2433
2205
|
return [await loadSkill(skillsDir)];
|
|
2434
2206
|
}
|
|
2435
|
-
if (
|
|
2207
|
+
if (existsSync2(join2(skillsDir, SKILL_MARKER))) {
|
|
2436
2208
|
return [await loadSkill(skillsDir)];
|
|
2437
2209
|
}
|
|
2438
2210
|
const entries = await readdir2(skillsDir, { withFileTypes: true });
|
|
2439
2211
|
const skills = [];
|
|
2440
2212
|
for (const entry of [...entries].sort((a, b) => a.name.localeCompare(b.name))) {
|
|
2441
2213
|
if (!entry.isDirectory()) continue;
|
|
2442
|
-
const skillFile =
|
|
2443
|
-
if (!
|
|
2214
|
+
const skillFile = join2(skillsDir, entry.name, SKILL_MARKER);
|
|
2215
|
+
if (!existsSync2(skillFile)) continue;
|
|
2444
2216
|
try {
|
|
2445
2217
|
const skill = await loadSkill(skillFile);
|
|
2446
2218
|
skills.push(skill);
|
|
@@ -2561,19 +2333,11 @@ var HooksManager = class {
|
|
|
2561
2333
|
return void 0;
|
|
2562
2334
|
}
|
|
2563
2335
|
};
|
|
2564
|
-
var _globalHooks;
|
|
2565
|
-
function getGlobalHooks() {
|
|
2566
|
-
_globalHooks ??= new HooksManager();
|
|
2567
|
-
return _globalHooks;
|
|
2568
|
-
}
|
|
2569
|
-
function resetGlobalHooks() {
|
|
2570
|
-
_globalHooks = void 0;
|
|
2571
|
-
}
|
|
2572
2336
|
|
|
2573
2337
|
// src/extensions/runner.ts
|
|
2574
2338
|
import { execFile } from "child_process";
|
|
2575
2339
|
import {
|
|
2576
|
-
registerModel
|
|
2340
|
+
registerModel,
|
|
2577
2341
|
unregisterProviderModels,
|
|
2578
2342
|
registerApiProvider
|
|
2579
2343
|
} from "@rowan-agent/models";
|
|
@@ -2624,6 +2388,9 @@ function createExtensionRuntime() {
|
|
|
2624
2388
|
return runtime;
|
|
2625
2389
|
}
|
|
2626
2390
|
|
|
2391
|
+
// src/extensions/runner.ts
|
|
2392
|
+
import { parseModelRef as parseModelRef2 } from "@rowan-agent/models";
|
|
2393
|
+
|
|
2627
2394
|
// src/extensions/event-bus.ts
|
|
2628
2395
|
function createEventBus() {
|
|
2629
2396
|
const listeners = /* @__PURE__ */ new Map();
|
|
@@ -2705,7 +2472,7 @@ function applyProviderRegistration(config) {
|
|
|
2705
2472
|
registerApiProvider({ protocol: config.protocol, stream: config.streamSimple });
|
|
2706
2473
|
}
|
|
2707
2474
|
for (const modelConfig of config.models) {
|
|
2708
|
-
|
|
2475
|
+
registerModel({
|
|
2709
2476
|
id: modelConfig.id,
|
|
2710
2477
|
name: modelConfig.name,
|
|
2711
2478
|
protocol: config.protocol,
|
|
@@ -2977,7 +2744,7 @@ var ExtensionRunner = class {
|
|
|
2977
2744
|
filePath: "",
|
|
2978
2745
|
baseDir: "",
|
|
2979
2746
|
content: "",
|
|
2980
|
-
...def.model ? { model:
|
|
2747
|
+
...def.model ? { model: parseModelRef2(def.model) } : {}
|
|
2981
2748
|
};
|
|
2982
2749
|
}
|
|
2983
2750
|
// ---------------------------------------------------------------------------
|
|
@@ -3300,10 +3067,10 @@ function createExtensionRunner(options) {
|
|
|
3300
3067
|
}
|
|
3301
3068
|
|
|
3302
3069
|
// src/extensions/loader.ts
|
|
3303
|
-
import { existsSync as
|
|
3304
|
-
import { readdir as readdir3, readFile as
|
|
3070
|
+
import { existsSync as existsSync3, readFileSync } from "fs";
|
|
3071
|
+
import { readdir as readdir3, readFile as readFile3, stat as stat2 } from "fs/promises";
|
|
3305
3072
|
import { fileURLToPath } from "url";
|
|
3306
|
-
import { dirname as
|
|
3073
|
+
import { basename as basename3, dirname as dirname5, extname as extname2, join as join3, resolve as resolve5 } from "path";
|
|
3307
3074
|
import { createJiti } from "jiti";
|
|
3308
3075
|
function isExtensionFile(path) {
|
|
3309
3076
|
const extension = extname2(path).toLowerCase();
|
|
@@ -3313,12 +3080,12 @@ function isSyntheticPath(path) {
|
|
|
3313
3080
|
return path.startsWith("<") && path.endsWith(">");
|
|
3314
3081
|
}
|
|
3315
3082
|
function readManifestSync(dir) {
|
|
3316
|
-
const manifestPath =
|
|
3317
|
-
if (!
|
|
3083
|
+
const manifestPath = join3(dir, "package.json");
|
|
3084
|
+
if (!existsSync3(manifestPath)) {
|
|
3318
3085
|
return void 0;
|
|
3319
3086
|
}
|
|
3320
3087
|
try {
|
|
3321
|
-
const content =
|
|
3088
|
+
const content = readFileSync(manifestPath, "utf8");
|
|
3322
3089
|
const pkg = JSON.parse(content);
|
|
3323
3090
|
const rowan = pkg.rowan;
|
|
3324
3091
|
if (!rowan) return void 0;
|
|
@@ -3336,7 +3103,7 @@ function jitiAliases() {
|
|
|
3336
3103
|
return {};
|
|
3337
3104
|
}
|
|
3338
3105
|
const agentSourcePath = fileURLToPath(new URL("../index.ts", moduleUrl));
|
|
3339
|
-
if (!
|
|
3106
|
+
if (!existsSync3(agentSourcePath)) {
|
|
3340
3107
|
return {};
|
|
3341
3108
|
}
|
|
3342
3109
|
return { "@rowan-agent/agent": agentSourcePath };
|
|
@@ -3344,7 +3111,9 @@ function jitiAliases() {
|
|
|
3344
3111
|
var sharedJiti;
|
|
3345
3112
|
function getJiti() {
|
|
3346
3113
|
sharedJiti ??= createJiti(import.meta.url || process.cwd(), {
|
|
3114
|
+
fsCache: false,
|
|
3347
3115
|
moduleCache: false,
|
|
3116
|
+
tryNative: false,
|
|
3348
3117
|
alias: jitiAliases()
|
|
3349
3118
|
});
|
|
3350
3119
|
return sharedJiti;
|
|
@@ -3355,21 +3124,21 @@ async function loadExtensionModule(extensionPath) {
|
|
|
3355
3124
|
return typeof module === "function" ? module : void 0;
|
|
3356
3125
|
}
|
|
3357
3126
|
async function readPackageManifest(path) {
|
|
3358
|
-
if (!
|
|
3127
|
+
if (!existsSync3(path)) {
|
|
3359
3128
|
return void 0;
|
|
3360
3129
|
}
|
|
3361
|
-
const content = await
|
|
3130
|
+
const content = await readFile3(path, "utf8");
|
|
3362
3131
|
return JSON.parse(content);
|
|
3363
3132
|
}
|
|
3364
3133
|
async function resolveExtensionEntries(dir) {
|
|
3365
|
-
const manifest = await readPackageManifest(
|
|
3134
|
+
const manifest = await readPackageManifest(join3(dir, "package.json"));
|
|
3366
3135
|
const declared = manifest?.rowan?.extensions;
|
|
3367
3136
|
if (declared && declared.length > 0) {
|
|
3368
3137
|
return declared.map((entry) => resolve5(dir, entry));
|
|
3369
3138
|
}
|
|
3370
3139
|
for (const name of ["index.ts", "index.js"]) {
|
|
3371
|
-
const entry =
|
|
3372
|
-
if (
|
|
3140
|
+
const entry = join3(dir, name);
|
|
3141
|
+
if (existsSync3(entry)) {
|
|
3373
3142
|
return [entry];
|
|
3374
3143
|
}
|
|
3375
3144
|
}
|
|
@@ -3384,7 +3153,7 @@ async function discoverExtensionsInDir(dir) {
|
|
|
3384
3153
|
});
|
|
3385
3154
|
const paths = [];
|
|
3386
3155
|
for (const entry of [...entries].sort((a, b) => a.name.localeCompare(b.name))) {
|
|
3387
|
-
const entryPath =
|
|
3156
|
+
const entryPath = join3(dir, entry.name);
|
|
3388
3157
|
if ((entry.isFile() || entry.isSymbolicLink()) && isExtensionFile(entry.name)) {
|
|
3389
3158
|
paths.push(entryPath);
|
|
3390
3159
|
continue;
|
|
@@ -3404,8 +3173,8 @@ async function discoverExtensionsInDir(dir) {
|
|
|
3404
3173
|
function loadExtensionFromFactory(factory, cwd, extensionPath = "<inline>") {
|
|
3405
3174
|
const resolvedCwd = resolve5(cwd);
|
|
3406
3175
|
const resolvedPath = isSyntheticPath(extensionPath) ? extensionPath : resolve5(resolvedCwd, extensionPath);
|
|
3407
|
-
const name = isSyntheticPath(extensionPath) ? extensionPath :
|
|
3408
|
-
const manifestDir = isSyntheticPath(extensionPath) ? resolvedCwd :
|
|
3176
|
+
const name = isSyntheticPath(extensionPath) ? extensionPath : basename3(dirname5(resolvedPath)) || "unknown";
|
|
3177
|
+
const manifestDir = isSyntheticPath(extensionPath) ? resolvedCwd : dirname5(resolvedPath);
|
|
3409
3178
|
const manifest = readManifestSync(manifestDir);
|
|
3410
3179
|
return {
|
|
3411
3180
|
path: resolvedPath,
|
|
@@ -3443,14 +3212,14 @@ async function loadExtensionsFromPath(path) {
|
|
|
3443
3212
|
const resolvedPath = resolve5(path);
|
|
3444
3213
|
const info = await stat2(resolvedPath);
|
|
3445
3214
|
if (!info.isDirectory()) {
|
|
3446
|
-
return loadExtensions([resolvedPath],
|
|
3215
|
+
return loadExtensions([resolvedPath], dirname5(resolvedPath));
|
|
3447
3216
|
}
|
|
3448
3217
|
const entries = await resolveExtensionEntries(resolvedPath) ?? await discoverExtensionsInDir(resolvedPath);
|
|
3449
3218
|
return loadExtensions(entries, resolvedPath);
|
|
3450
3219
|
}
|
|
3451
3220
|
|
|
3452
|
-
// src/agent.ts
|
|
3453
|
-
var
|
|
3221
|
+
// src/agent-execution.ts
|
|
3222
|
+
var AgentExecution = class {
|
|
3454
3223
|
state;
|
|
3455
3224
|
options;
|
|
3456
3225
|
listeners = /* @__PURE__ */ new Set();
|
|
@@ -3460,6 +3229,17 @@ var Agent = class {
|
|
|
3460
3229
|
extensionRunner;
|
|
3461
3230
|
loadedExtensions;
|
|
3462
3231
|
loadedExtensionsCwd;
|
|
3232
|
+
runtimeRunId;
|
|
3233
|
+
get id() {
|
|
3234
|
+
const agentId = this.state.agentId;
|
|
3235
|
+
if (!agentId) throw new Error("Agent has no durable identity.");
|
|
3236
|
+
return agentId;
|
|
3237
|
+
}
|
|
3238
|
+
get sessionId() {
|
|
3239
|
+
const sessionId = this.state.sessionId;
|
|
3240
|
+
if (!sessionId) throw new Error("Agent has no Session.");
|
|
3241
|
+
return sessionId;
|
|
3242
|
+
}
|
|
3463
3243
|
static loadSkills(targetPath) {
|
|
3464
3244
|
return loadSkills(targetPath);
|
|
3465
3245
|
}
|
|
@@ -3476,6 +3256,7 @@ var Agent = class {
|
|
|
3476
3256
|
context
|
|
3477
3257
|
};
|
|
3478
3258
|
this.state = {
|
|
3259
|
+
...this.options.agentId ? { agentId: this.options.agentId } : {},
|
|
3479
3260
|
...this.options.sessionId ? { sessionId: this.options.sessionId } : {},
|
|
3480
3261
|
context: prepareAgentContext(context),
|
|
3481
3262
|
model: this.options.model,
|
|
@@ -3511,17 +3292,37 @@ var Agent = class {
|
|
|
3511
3292
|
this.appendUserMessage(input);
|
|
3512
3293
|
return this.run(options);
|
|
3513
3294
|
}
|
|
3514
|
-
runWithMessage(message, options) {
|
|
3295
|
+
runWithMessage(message, options, internalHooks = {}) {
|
|
3515
3296
|
const activeRun = this.activeRun;
|
|
3516
3297
|
if (activeRun?.resume) {
|
|
3298
|
+
if (internalHooks.onSuspend) activeRun.onSuspend = internalHooks.onSuspend;
|
|
3517
3299
|
activeRun.resume([message]);
|
|
3518
3300
|
return activeRun.promise;
|
|
3519
3301
|
}
|
|
3520
3302
|
if (activeRun) {
|
|
3521
3303
|
return Promise.reject(new Error("Agent is already running."));
|
|
3522
3304
|
}
|
|
3523
|
-
this.
|
|
3524
|
-
|
|
3305
|
+
if (!this.options.context.messages.some((candidate) => candidate.id === message.id)) {
|
|
3306
|
+
this.appendMessage(message);
|
|
3307
|
+
}
|
|
3308
|
+
return this.run(options, internalHooks);
|
|
3309
|
+
}
|
|
3310
|
+
/** Internal Runtime seam: executes one leased Agent Input. */
|
|
3311
|
+
async executeAgentInput(input, runId, control, executionState) {
|
|
3312
|
+
this.runtimeRunId = runId;
|
|
3313
|
+
this.options.sessionState = executionState ? restoreSessionState(executionState) : void 0;
|
|
3314
|
+
const internal = {
|
|
3315
|
+
onSuspend: (reason, state, inputRequest) => control.suspend(
|
|
3316
|
+
reason,
|
|
3317
|
+
state ? snapshotExecutionState(state) : void 0,
|
|
3318
|
+
inputRequest
|
|
3319
|
+
)
|
|
3320
|
+
};
|
|
3321
|
+
try {
|
|
3322
|
+
return (await this.runWithMessage(input, void 0, internal)).outcome;
|
|
3323
|
+
} finally {
|
|
3324
|
+
if (!this.activeRun) this.runtimeRunId = void 0;
|
|
3325
|
+
}
|
|
3525
3326
|
}
|
|
3526
3327
|
resetInitialization() {
|
|
3527
3328
|
this.state.initialized = false;
|
|
@@ -3593,6 +3394,13 @@ var Agent = class {
|
|
|
3593
3394
|
getSessionId() {
|
|
3594
3395
|
return this.state.sessionId;
|
|
3595
3396
|
}
|
|
3397
|
+
getAgentId() {
|
|
3398
|
+
return this.state.agentId;
|
|
3399
|
+
}
|
|
3400
|
+
/** Internal Runtime hook for associating Tool Calls with the leased Run. */
|
|
3401
|
+
getRuntimeRunId() {
|
|
3402
|
+
return this.runtimeRunId;
|
|
3403
|
+
}
|
|
3596
3404
|
setModel(model) {
|
|
3597
3405
|
this.options = {
|
|
3598
3406
|
...this.options,
|
|
@@ -3740,25 +3548,34 @@ var Agent = class {
|
|
|
3740
3548
|
const message = error instanceof Error ? error.message : "Agent run failed.";
|
|
3741
3549
|
this.state.error = message;
|
|
3742
3550
|
}
|
|
3743
|
-
runWithLifecycle(executor) {
|
|
3551
|
+
runWithLifecycle(executor, hooks = {}) {
|
|
3744
3552
|
if (this.activeRun) {
|
|
3745
3553
|
return Promise.reject(new Error("Agent is already running."));
|
|
3746
3554
|
}
|
|
3747
3555
|
const abortController = new AbortController();
|
|
3748
|
-
|
|
3749
|
-
|
|
3750
|
-
|
|
3751
|
-
activeRun
|
|
3752
|
-
|
|
3753
|
-
|
|
3754
|
-
|
|
3556
|
+
let suspensionRequested = false;
|
|
3557
|
+
const waitForInput = (state, inputRequest) => {
|
|
3558
|
+
const waiting = new Promise((resolve7) => {
|
|
3559
|
+
const activeRun = this.activeRun;
|
|
3560
|
+
activeRun.resume = (messages) => {
|
|
3561
|
+
activeRun.resume = void 0;
|
|
3562
|
+
suspensionRequested = false;
|
|
3563
|
+
resolve7(messages);
|
|
3564
|
+
};
|
|
3565
|
+
});
|
|
3566
|
+
if (!suspensionRequested) {
|
|
3567
|
+
suspensionRequested = true;
|
|
3568
|
+
void this.activeRun?.onSuspend?.("Agent requested input.", state, inputRequest);
|
|
3569
|
+
}
|
|
3570
|
+
return waiting;
|
|
3571
|
+
};
|
|
3755
3572
|
const promise = Promise.resolve().then(() => executor({ signal: abortController.signal, waitForInput })).catch((error) => {
|
|
3756
3573
|
this.handleRunFailure(error, abortController.signal.aborted);
|
|
3757
3574
|
throw error;
|
|
3758
3575
|
}).finally(() => {
|
|
3759
3576
|
this.finishRun();
|
|
3760
3577
|
});
|
|
3761
|
-
this.activeRun = { promise, abortController };
|
|
3578
|
+
this.activeRun = { promise, abortController, onSuspend: hooks.onSuspend };
|
|
3762
3579
|
this.state.running = true;
|
|
3763
3580
|
return promise;
|
|
3764
3581
|
}
|
|
@@ -3785,13 +3602,14 @@ var Agent = class {
|
|
|
3785
3602
|
this.loadedExtensions = extensions;
|
|
3786
3603
|
this.loadedExtensionsCwd = cwd;
|
|
3787
3604
|
}
|
|
3788
|
-
run(config) {
|
|
3605
|
+
run(config, internalHooks = {}) {
|
|
3789
3606
|
return this.runWithLifecycle(async ({ signal, waitForInput }) => {
|
|
3790
3607
|
const resolved = this.resolveRunConfig(config);
|
|
3791
3608
|
const extensions = resolved.extensions ?? [];
|
|
3792
3609
|
await this.loadExtensions(extensions, resolved.cwd);
|
|
3793
3610
|
const runContext = prepareAgentContext(resolved.context, this.extensionRunner);
|
|
3794
|
-
const
|
|
3611
|
+
const resumingSuspendedRun = resolved.sessionState?.status === "suspended";
|
|
3612
|
+
const entryPhaseId = resumingSuspendedRun ? resolved.sessionState?.currentPhase || runContext.phases?.entryPhaseId || DEFAULT_PHASE_ID : this.state.initialized ? DEFAULT_PHASE_ID : runContext.phases?.entryPhaseId ?? DEFAULT_PHASE_ID;
|
|
3795
3613
|
const loopContext = {
|
|
3796
3614
|
...runContext,
|
|
3797
3615
|
phases: {
|
|
@@ -3836,6 +3654,7 @@ var Agent = class {
|
|
|
3836
3654
|
model: resolved.model,
|
|
3837
3655
|
stream: resolved.stream,
|
|
3838
3656
|
maxAttempts: resolved.maxAttempts,
|
|
3657
|
+
sessionState: resolved.sessionState,
|
|
3839
3658
|
signal,
|
|
3840
3659
|
beforeToolCall,
|
|
3841
3660
|
afterToolCall,
|
|
@@ -3846,7 +3665,8 @@ var Agent = class {
|
|
|
3846
3665
|
onMessage: resolved.onMessage,
|
|
3847
3666
|
onOutcome: resolved.onOutcome,
|
|
3848
3667
|
onModelTranscript: resolved.onModelTranscript,
|
|
3849
|
-
waitForInput
|
|
3668
|
+
waitForInput,
|
|
3669
|
+
runtime: resolved.runtime
|
|
3850
3670
|
});
|
|
3851
3671
|
const nextContext = {
|
|
3852
3672
|
...prepareAgentContext(resolved.context),
|
|
@@ -3859,16 +3679,27 @@ var Agent = class {
|
|
|
3859
3679
|
this.options = {
|
|
3860
3680
|
...resolved,
|
|
3861
3681
|
sessionId: result.sessionId,
|
|
3862
|
-
context: nextContext
|
|
3682
|
+
context: nextContext,
|
|
3683
|
+
sessionState: void 0
|
|
3863
3684
|
};
|
|
3864
3685
|
return result;
|
|
3865
|
-
});
|
|
3686
|
+
}, internalHooks);
|
|
3866
3687
|
}
|
|
3867
3688
|
abort(reason = "Aborted by caller.") {
|
|
3689
|
+
this.abortLocal(reason);
|
|
3690
|
+
}
|
|
3691
|
+
isSuspended() {
|
|
3692
|
+
return Boolean(this.activeRun?.resume);
|
|
3693
|
+
}
|
|
3694
|
+
abortLocal(reason) {
|
|
3868
3695
|
const activeRun = this.activeRun;
|
|
3869
3696
|
activeRun?.resume?.([]);
|
|
3870
3697
|
activeRun?.abortController.abort(reason);
|
|
3871
3698
|
}
|
|
3699
|
+
/** Internal Runtime hook; public callers abort a precise AgentRun. */
|
|
3700
|
+
abortFromRuntime(reason = "Agent Runtime aborted the Run.") {
|
|
3701
|
+
this.abortLocal(reason);
|
|
3702
|
+
}
|
|
3872
3703
|
/**
|
|
3873
3704
|
* Get formatted skill content for LLM consumption.
|
|
3874
3705
|
*
|
|
@@ -3934,6 +3765,50 @@ ${additionalInstructions}` : content;
|
|
|
3934
3765
|
};
|
|
3935
3766
|
}
|
|
3936
3767
|
};
|
|
3768
|
+
function restoreSessionState(state) {
|
|
3769
|
+
const persisted = parsePersistedExecutionState(state);
|
|
3770
|
+
return {
|
|
3771
|
+
...persisted,
|
|
3772
|
+
status: "suspended",
|
|
3773
|
+
metrics: {
|
|
3774
|
+
...persisted.metrics,
|
|
3775
|
+
phaseTransitions: persisted.metrics.phaseTransitions.map((transition) => ({ ...transition }))
|
|
3776
|
+
},
|
|
3777
|
+
...persisted.continuation ? {
|
|
3778
|
+
continuation: {
|
|
3779
|
+
...persisted.continuation,
|
|
3780
|
+
previousResults: persisted.continuation.previousResults.map((result) => ({ ...result }))
|
|
3781
|
+
}
|
|
3782
|
+
} : {}
|
|
3783
|
+
};
|
|
3784
|
+
}
|
|
3785
|
+
function snapshotExecutionState(state) {
|
|
3786
|
+
return {
|
|
3787
|
+
version: 1,
|
|
3788
|
+
currentPhase: state.currentPhase,
|
|
3789
|
+
attempt: state.attempt,
|
|
3790
|
+
metrics: {
|
|
3791
|
+
...state.metrics,
|
|
3792
|
+
phaseTransitions: state.metrics.phaseTransitions.map((transition) => ({ ...transition }))
|
|
3793
|
+
},
|
|
3794
|
+
...state.continuation ? {
|
|
3795
|
+
continuation: {
|
|
3796
|
+
...state.continuation,
|
|
3797
|
+
previousResults: state.continuation.previousResults.map((result) => ({ ...result }))
|
|
3798
|
+
}
|
|
3799
|
+
} : {}
|
|
3800
|
+
};
|
|
3801
|
+
}
|
|
3802
|
+
function parsePersistedExecutionState(state) {
|
|
3803
|
+
if (!state || state.version !== 1 || typeof state.currentPhase !== "string" || typeof state.attempt !== "number") {
|
|
3804
|
+
throw new Error("Invalid Agent Run execution checkpoint.");
|
|
3805
|
+
}
|
|
3806
|
+
const metrics = state.metrics;
|
|
3807
|
+
if (!metrics || !Array.isArray(metrics.phaseTransitions)) {
|
|
3808
|
+
throw new Error("Invalid Agent Run execution checkpoint metrics.");
|
|
3809
|
+
}
|
|
3810
|
+
return state;
|
|
3811
|
+
}
|
|
3937
3812
|
function clonePhaseRegistry(registry) {
|
|
3938
3813
|
return {
|
|
3939
3814
|
phases: new Map(registry.phases),
|
|
@@ -3962,9 +3837,130 @@ function prepareAgentContext(context, extensionRunner) {
|
|
|
3962
3837
|
};
|
|
3963
3838
|
}
|
|
3964
3839
|
|
|
3840
|
+
// src/agent.ts
|
|
3841
|
+
var AGENT_CONSTRUCTION = /* @__PURE__ */ Symbol("rowan.agent.construction");
|
|
3842
|
+
var ATTACH_AGENT = /* @__PURE__ */ Symbol("rowan.agent.attach");
|
|
3843
|
+
var Agent = class _Agent {
|
|
3844
|
+
constructor(token, id, sessionId, execution, submitInput) {
|
|
3845
|
+
this.id = id;
|
|
3846
|
+
this.sessionId = sessionId;
|
|
3847
|
+
this.execution = execution;
|
|
3848
|
+
this.submitInput = submitInput;
|
|
3849
|
+
if (token !== AGENT_CONSTRUCTION) {
|
|
3850
|
+
throw new Error("Agent lifecycle is owned by AgentRuntime.");
|
|
3851
|
+
}
|
|
3852
|
+
}
|
|
3853
|
+
id;
|
|
3854
|
+
sessionId;
|
|
3855
|
+
execution;
|
|
3856
|
+
submitInput;
|
|
3857
|
+
static [ATTACH_AGENT](id, sessionId, execution, submitInput) {
|
|
3858
|
+
return new _Agent(AGENT_CONSTRUCTION, id, sessionId, execution, submitInput);
|
|
3859
|
+
}
|
|
3860
|
+
static loadSkills(targetPath) {
|
|
3861
|
+
return AgentExecution.loadSkills(targetPath);
|
|
3862
|
+
}
|
|
3863
|
+
static loadPhases(targetPath) {
|
|
3864
|
+
return AgentExecution.loadPhases(targetPath);
|
|
3865
|
+
}
|
|
3866
|
+
static loadExtensions(targetPath) {
|
|
3867
|
+
return AgentExecution.loadExtensions(targetPath);
|
|
3868
|
+
}
|
|
3869
|
+
subscribe(listener) {
|
|
3870
|
+
return this.execution.subscribe(listener);
|
|
3871
|
+
}
|
|
3872
|
+
flushEvents() {
|
|
3873
|
+
return this.execution.flushEvents();
|
|
3874
|
+
}
|
|
3875
|
+
send(input) {
|
|
3876
|
+
const message = typeof input === "string" ? createMessage("user", input) : input;
|
|
3877
|
+
return this.submitInput(message);
|
|
3878
|
+
}
|
|
3879
|
+
};
|
|
3880
|
+
async function attachAgent(input) {
|
|
3881
|
+
const restoredContext = await input.manager.buildAgentContext({
|
|
3882
|
+
tools: input.options.context.tools,
|
|
3883
|
+
skills: input.options.context.skills
|
|
3884
|
+
});
|
|
3885
|
+
const context = {
|
|
3886
|
+
...restoredContext,
|
|
3887
|
+
systemPrompt: input.options.context.systemPrompt || restoredContext.systemPrompt,
|
|
3888
|
+
phases: input.options.context.phases
|
|
3889
|
+
};
|
|
3890
|
+
const persistInput = async (message) => {
|
|
3891
|
+
const context2 = await input.manager.buildAgentContext();
|
|
3892
|
+
if (context2.messages.some((candidate) => candidate.id === message.id)) return;
|
|
3893
|
+
await input.manager.appendMessage(message);
|
|
3894
|
+
};
|
|
3895
|
+
let execution;
|
|
3896
|
+
execution = new AgentExecution({
|
|
3897
|
+
...input.options,
|
|
3898
|
+
context,
|
|
3899
|
+
agentId: input.agentId,
|
|
3900
|
+
sessionId: input.sessionId,
|
|
3901
|
+
runtime: {
|
|
3902
|
+
tools: async ({ config, toolCall }) => {
|
|
3903
|
+
const tool = config.context.tools.find((candidate) => candidate.name === toolCall.name);
|
|
3904
|
+
const runId = execution.getRuntimeRunId();
|
|
3905
|
+
if (!tool || !runId) {
|
|
3906
|
+
const message = `Tool ${toolCall.name} is not available to this Agent Run.`;
|
|
3907
|
+
return {
|
|
3908
|
+
toolCallId: toolCall.id,
|
|
3909
|
+
toolName: toolCall.name,
|
|
3910
|
+
ok: false,
|
|
3911
|
+
content: message,
|
|
3912
|
+
error: message
|
|
3913
|
+
};
|
|
3914
|
+
}
|
|
3915
|
+
return input.executeTool({
|
|
3916
|
+
agentId: input.agentId,
|
|
3917
|
+
runId,
|
|
3918
|
+
tool,
|
|
3919
|
+
toolCall,
|
|
3920
|
+
context: config.context,
|
|
3921
|
+
beforeToolCall: config.beforeToolCall,
|
|
3922
|
+
afterToolCall: config.afterToolCall,
|
|
3923
|
+
signal: config.signal
|
|
3924
|
+
});
|
|
3925
|
+
}
|
|
3926
|
+
},
|
|
3927
|
+
onMessage: async (message) => {
|
|
3928
|
+
await input.manager.appendMessage(message);
|
|
3929
|
+
await input.options.onMessage?.(message);
|
|
3930
|
+
},
|
|
3931
|
+
onInput: persistInput,
|
|
3932
|
+
onOutcome: async (outcome) => {
|
|
3933
|
+
await input.manager.appendOutcome(outcome);
|
|
3934
|
+
await input.options.onOutcome?.(outcome);
|
|
3935
|
+
},
|
|
3936
|
+
onModelTranscript: async (transcript, meta) => {
|
|
3937
|
+
await input.manager.appendModelTranscript(transcript, meta);
|
|
3938
|
+
await input.options.onModelTranscript?.(transcript, meta);
|
|
3939
|
+
}
|
|
3940
|
+
});
|
|
3941
|
+
const agent = Agent[ATTACH_AGENT](
|
|
3942
|
+
input.agentId,
|
|
3943
|
+
input.sessionId,
|
|
3944
|
+
execution,
|
|
3945
|
+
(message) => input.submit(message, () => persistInput(message))
|
|
3946
|
+
);
|
|
3947
|
+
return {
|
|
3948
|
+
agent,
|
|
3949
|
+
binding: {
|
|
3950
|
+
abort: (reason) => execution.abortFromRuntime(reason),
|
|
3951
|
+
isSuspended: () => execution.isSuspended(),
|
|
3952
|
+
persistInput,
|
|
3953
|
+
execute: (message, runId, control, executionState) => execution.executeAgentInput(message, runId, control, executionState)
|
|
3954
|
+
}
|
|
3955
|
+
};
|
|
3956
|
+
}
|
|
3957
|
+
|
|
3958
|
+
// src/harness/session/jsonl.ts
|
|
3959
|
+
import { appendFile, mkdir as mkdir2, readFile as readFile4, readdir as readdir4, stat as stat3, unlink, writeFile as writeFile2 } from "fs/promises";
|
|
3960
|
+
import { join as join4, relative as relative3, resolve as resolve6, sep as sep3 } from "path";
|
|
3961
|
+
|
|
3965
3962
|
// src/harness/session/session.ts
|
|
3966
3963
|
import Type3 from "typebox";
|
|
3967
|
-
var SESSION_SCHEMA_VERSION = "0.4.4";
|
|
3968
3964
|
var AgentMessageSchema = Type3.Object({
|
|
3969
3965
|
id: Type3.String(),
|
|
3970
3966
|
role: Type3.Union([
|
|
@@ -3985,39 +3981,6 @@ var SkillSchema = Type3.Object({
|
|
|
3985
3981
|
content: Type3.String(),
|
|
3986
3982
|
disableModelInvocation: Type3.Boolean()
|
|
3987
3983
|
});
|
|
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
3984
|
|
|
4022
3985
|
// src/harness/session/session-manager.ts
|
|
4023
3986
|
var SESSION_MANAGER_SCHEMA_VERSION = "0.4.4";
|
|
@@ -4214,8 +4177,6 @@ function summarizeSessionManagerRecords(records) {
|
|
|
4214
4177
|
}
|
|
4215
4178
|
|
|
4216
4179
|
// 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
4180
|
var SESSION_ID_PATTERN = /^ses_[A-Za-z0-9_-]+$/;
|
|
4220
4181
|
function isPathInside(parent, child) {
|
|
4221
4182
|
const relativePath = relative3(parent, child);
|
|
@@ -4237,7 +4198,7 @@ function parseRecord(line) {
|
|
|
4237
4198
|
return value;
|
|
4238
4199
|
}
|
|
4239
4200
|
async function readRecords(path) {
|
|
4240
|
-
const text = await
|
|
4201
|
+
const text = await readFile4(path, "utf8").catch((error) => {
|
|
4241
4202
|
if (error.code === "ENOENT") {
|
|
4242
4203
|
return void 0;
|
|
4243
4204
|
}
|
|
@@ -4326,7 +4287,7 @@ var LocalJsonlSessionManager = class _LocalJsonlSessionManager {
|
|
|
4326
4287
|
});
|
|
4327
4288
|
const sessions = await Promise.all(
|
|
4328
4289
|
entries.filter((entry) => entry.isFile() && entry.name.endsWith(".jsonl")).map(async (entry) => {
|
|
4329
|
-
const path =
|
|
4290
|
+
const path = join4(sessionsDir, entry.name);
|
|
4330
4291
|
const records = await readRecords(path);
|
|
4331
4292
|
return records ? summarizeSessionManagerRecords(records) : void 0;
|
|
4332
4293
|
})
|
|
@@ -4416,114 +4377,2147 @@ var LocalJsonlSessionManager = class _LocalJsonlSessionManager {
|
|
|
4416
4377
|
}
|
|
4417
4378
|
};
|
|
4418
4379
|
|
|
4419
|
-
// src/
|
|
4420
|
-
var
|
|
4421
|
-
|
|
4422
|
-
|
|
4423
|
-
|
|
4424
|
-
|
|
4425
|
-
|
|
4426
|
-
isComplete;
|
|
4427
|
-
extractResult;
|
|
4428
|
-
constructor(isComplete, extractResult) {
|
|
4429
|
-
this.isComplete = isComplete;
|
|
4430
|
-
this.extractResult = extractResult;
|
|
4431
|
-
this.finalResultPromise = new Promise((resolve7) => {
|
|
4432
|
-
this.resolveFinalResult = resolve7;
|
|
4433
|
-
});
|
|
4380
|
+
// src/harness/session/store.ts
|
|
4381
|
+
var InMemorySessionStore = class {
|
|
4382
|
+
sessions = /* @__PURE__ */ new Map();
|
|
4383
|
+
async create(input) {
|
|
4384
|
+
const session = InMemorySessionManager.create(input);
|
|
4385
|
+
this.sessions.set(session.getSessionId(), session);
|
|
4386
|
+
return session;
|
|
4434
4387
|
}
|
|
4435
|
-
|
|
4436
|
-
|
|
4437
|
-
if (this.isComplete(event)) {
|
|
4438
|
-
this.done = true;
|
|
4439
|
-
this.resolveFinalResult(this.extractResult(event));
|
|
4440
|
-
}
|
|
4441
|
-
const waiter = this.waiting.shift();
|
|
4442
|
-
if (waiter) {
|
|
4443
|
-
waiter({ value: event, done: false });
|
|
4444
|
-
} else {
|
|
4445
|
-
this.queue.push(event);
|
|
4446
|
-
}
|
|
4388
|
+
async open(sessionId) {
|
|
4389
|
+
return this.sessions.get(sessionId);
|
|
4447
4390
|
}
|
|
4448
|
-
|
|
4449
|
-
|
|
4450
|
-
|
|
4451
|
-
|
|
4452
|
-
}
|
|
4453
|
-
while (this.waiting.length > 0) {
|
|
4454
|
-
const waiter = this.waiting.shift();
|
|
4455
|
-
waiter({ value: void 0, done: true });
|
|
4456
|
-
}
|
|
4391
|
+
};
|
|
4392
|
+
var JsonlSessionStore = class {
|
|
4393
|
+
constructor(sessionsDir) {
|
|
4394
|
+
this.sessionsDir = sessionsDir;
|
|
4457
4395
|
}
|
|
4458
|
-
|
|
4459
|
-
|
|
4460
|
-
|
|
4461
|
-
yield this.queue.shift();
|
|
4462
|
-
} else if (this.done) {
|
|
4463
|
-
return;
|
|
4464
|
-
} else {
|
|
4465
|
-
const result = await new Promise((resolve7) => this.waiting.push(resolve7));
|
|
4466
|
-
if (result.done) return;
|
|
4467
|
-
yield result.value;
|
|
4468
|
-
}
|
|
4469
|
-
}
|
|
4396
|
+
sessionsDir;
|
|
4397
|
+
create(input) {
|
|
4398
|
+
return LocalJsonlSessionManager.create(this.sessionsDir, input);
|
|
4470
4399
|
}
|
|
4471
|
-
|
|
4472
|
-
return this.
|
|
4400
|
+
open(sessionId) {
|
|
4401
|
+
return LocalJsonlSessionManager.open(this.sessionsDir, sessionId);
|
|
4473
4402
|
}
|
|
4474
|
-
|
|
4475
|
-
|
|
4476
|
-
constructor() {
|
|
4477
|
-
super(
|
|
4478
|
-
(event) => event.type === "agent_end",
|
|
4479
|
-
(event) => event.type === "agent_end" ? event.messages : []
|
|
4480
|
-
);
|
|
4403
|
+
list() {
|
|
4404
|
+
return LocalJsonlSessionManager.list(this.sessionsDir);
|
|
4481
4405
|
}
|
|
4482
4406
|
};
|
|
4483
4407
|
|
|
4484
|
-
// src/
|
|
4485
|
-
import {
|
|
4486
|
-
|
|
4487
|
-
|
|
4488
|
-
|
|
4408
|
+
// src/runtime/agent-runtime.ts
|
|
4409
|
+
import { createModelStream } from "@rowan-agent/models";
|
|
4410
|
+
|
|
4411
|
+
// src/runtime/tool-runtime.ts
|
|
4412
|
+
function failed(toolCall, message) {
|
|
4413
|
+
return {
|
|
4414
|
+
toolCallId: toolCall.id,
|
|
4415
|
+
toolName: toolCall.name,
|
|
4416
|
+
ok: false,
|
|
4417
|
+
content: message,
|
|
4418
|
+
error: message
|
|
4419
|
+
};
|
|
4420
|
+
}
|
|
4421
|
+
function positiveLimit(value, fallback) {
|
|
4422
|
+
if (value === void 0) return fallback;
|
|
4423
|
+
if (!Number.isFinite(value) || value <= 0) {
|
|
4424
|
+
throw new Error("Tool Runtime concurrency limits must be positive finite numbers.");
|
|
4425
|
+
}
|
|
4426
|
+
return Math.floor(value);
|
|
4427
|
+
}
|
|
4428
|
+
var ToolRuntime = class {
|
|
4429
|
+
constructor(stateStore, policy = {}, onTransition = () => void 0) {
|
|
4430
|
+
this.stateStore = stateStore;
|
|
4431
|
+
this.onTransition = onTransition;
|
|
4432
|
+
this.maxConcurrent = positiveLimit(policy.maxConcurrent, Number.MAX_SAFE_INTEGER);
|
|
4433
|
+
this.perToolMaxConcurrent = Object.fromEntries(
|
|
4434
|
+
Object.entries(policy.perToolMaxConcurrent ?? {}).map(([name, value]) => [name, positiveLimit(value, 1)])
|
|
4435
|
+
);
|
|
4436
|
+
this.allowedTools = policy.allowedTools ? new Set(policy.allowedTools) : void 0;
|
|
4437
|
+
}
|
|
4438
|
+
stateStore;
|
|
4439
|
+
onTransition;
|
|
4440
|
+
maxConcurrent;
|
|
4441
|
+
perToolMaxConcurrent;
|
|
4442
|
+
running = 0;
|
|
4443
|
+
runningByTool = /* @__PURE__ */ new Map();
|
|
4444
|
+
waiters = [];
|
|
4445
|
+
activeToolCalls = /* @__PURE__ */ new Map();
|
|
4446
|
+
allowedTools;
|
|
4447
|
+
async execute(input) {
|
|
4448
|
+
const call = await this.stateStore.createToolCall({
|
|
4449
|
+
agentId: input.agentId,
|
|
4450
|
+
runId: input.runId,
|
|
4451
|
+
name: input.toolCall.name,
|
|
4452
|
+
args: input.toolCall.args
|
|
4453
|
+
});
|
|
4454
|
+
await this.onTransition();
|
|
4455
|
+
if (!input.tool || this.allowedTools && !this.allowedTools.has(input.tool.name)) {
|
|
4456
|
+
const result = failed(input.toolCall, `Tool ${input.toolCall.name} is not permitted by Runtime policy.`);
|
|
4457
|
+
await this.stateStore.completeToolCall({ toolCallId: call.id, result, state: "failed" });
|
|
4458
|
+
await this.onTransition();
|
|
4459
|
+
return result;
|
|
4460
|
+
}
|
|
4461
|
+
const before = await input.beforeToolCall?.({ tool: input.tool, args: input.toolCall.args });
|
|
4462
|
+
if (before && !before.allow) {
|
|
4463
|
+
const result = failed(input.toolCall, before.reason);
|
|
4464
|
+
await this.stateStore.completeToolCall({ toolCallId: call.id, result, state: "failed" });
|
|
4465
|
+
await this.onTransition();
|
|
4466
|
+
return result;
|
|
4467
|
+
}
|
|
4468
|
+
let release;
|
|
4469
|
+
const controller = new AbortController();
|
|
4470
|
+
const forwardAbort = () => controller.abort(input.signal?.reason);
|
|
4471
|
+
input.signal?.addEventListener("abort", forwardAbort, { once: true });
|
|
4472
|
+
this.activeToolCalls.set(call.id, { runId: input.runId, controller });
|
|
4473
|
+
try {
|
|
4474
|
+
try {
|
|
4475
|
+
release = await this.acquire(input.tool.name, controller.signal);
|
|
4476
|
+
} catch (error) {
|
|
4477
|
+
const result2 = failed(input.toolCall, error instanceof Error ? error.message : "Tool execution was cancelled.");
|
|
4478
|
+
await this.stateStore.completeToolCall({ toolCallId: call.id, result: result2, state: "failed" });
|
|
4479
|
+
await this.onTransition();
|
|
4480
|
+
return result2;
|
|
4481
|
+
}
|
|
4482
|
+
if (controller.signal.aborted) {
|
|
4483
|
+
const result2 = failed(input.toolCall, "Tool execution was aborted before it started.");
|
|
4484
|
+
await this.stateStore.completeToolCall({ toolCallId: call.id, result: result2, state: "failed" });
|
|
4485
|
+
await this.onTransition();
|
|
4486
|
+
return result2;
|
|
4487
|
+
}
|
|
4488
|
+
await this.stateStore.startToolCall(call.id);
|
|
4489
|
+
await this.onTransition();
|
|
4490
|
+
let result;
|
|
4491
|
+
try {
|
|
4492
|
+
result = await input.tool.execute(input.toolCall.args, {
|
|
4493
|
+
skills: input.context.skills,
|
|
4494
|
+
toolCallId: call.id
|
|
4495
|
+
}, controller.signal);
|
|
4496
|
+
} catch (error) {
|
|
4497
|
+
if (controller.signal.aborted) {
|
|
4498
|
+
await this.stateStore.markToolCallIndeterminate({
|
|
4499
|
+
toolCallId: call.id,
|
|
4500
|
+
reason: error instanceof Error ? error.message : "Tool execution was interrupted."
|
|
4501
|
+
});
|
|
4502
|
+
await this.onTransition();
|
|
4503
|
+
return failed(input.toolCall, "Tool Call became indeterminate after abort.");
|
|
4504
|
+
}
|
|
4505
|
+
result = failed(input.toolCall, error instanceof Error ? error.message : "Tool execution failed.");
|
|
4506
|
+
}
|
|
4507
|
+
if (controller.signal.aborted) {
|
|
4508
|
+
await this.stateStore.markToolCallIndeterminate({ toolCallId: call.id, reason: "Tool execution was interrupted after it may have had an external effect." });
|
|
4509
|
+
await this.onTransition();
|
|
4510
|
+
return failed(input.toolCall, "Tool Call became indeterminate after abort.");
|
|
4511
|
+
}
|
|
4512
|
+
try {
|
|
4513
|
+
result = await input.afterToolCall?.({ tool: input.tool, result }) ?? result;
|
|
4514
|
+
} catch (error) {
|
|
4515
|
+
result = failed(input.toolCall, error instanceof Error ? error.message : "Tool result review failed.");
|
|
4516
|
+
}
|
|
4517
|
+
await this.stateStore.completeToolCall({
|
|
4518
|
+
toolCallId: call.id,
|
|
4519
|
+
result,
|
|
4520
|
+
state: result.ok ? "completed" : "failed"
|
|
4521
|
+
});
|
|
4522
|
+
await this.onTransition();
|
|
4523
|
+
return result;
|
|
4524
|
+
} finally {
|
|
4525
|
+
input.signal?.removeEventListener("abort", forwardAbort);
|
|
4526
|
+
this.activeToolCalls.delete(call.id);
|
|
4527
|
+
release?.();
|
|
4528
|
+
}
|
|
4529
|
+
}
|
|
4530
|
+
abortRun(runId) {
|
|
4531
|
+
for (const active of this.activeToolCalls.values()) {
|
|
4532
|
+
if (active.runId === runId) active.controller.abort("Agent Run aborted.");
|
|
4533
|
+
}
|
|
4534
|
+
}
|
|
4535
|
+
async acquire(toolName, signal) {
|
|
4536
|
+
if (signal.aborted) throw new Error("Tool execution aborted while waiting for capacity.");
|
|
4537
|
+
const limit = this.perToolMaxConcurrent[toolName] ?? this.maxConcurrent;
|
|
4538
|
+
if (this.running < this.maxConcurrent && (this.runningByTool.get(toolName) ?? 0) < limit) {
|
|
4539
|
+
return this.reserve(toolName);
|
|
4540
|
+
}
|
|
4541
|
+
return new Promise((resolve7, reject) => {
|
|
4542
|
+
let waiter;
|
|
4543
|
+
const onAbort = () => {
|
|
4544
|
+
const index = this.waiters.indexOf(waiter);
|
|
4545
|
+
if (index >= 0) this.waiters.splice(index, 1);
|
|
4546
|
+
reject(new Error("Tool execution aborted while waiting for capacity."));
|
|
4547
|
+
};
|
|
4548
|
+
waiter = { toolName, signal, onAbort, resolve: resolve7, reject };
|
|
4549
|
+
this.waiters.push(waiter);
|
|
4550
|
+
signal.addEventListener("abort", onAbort, { once: true });
|
|
4551
|
+
});
|
|
4552
|
+
}
|
|
4553
|
+
reserve(toolName) {
|
|
4554
|
+
this.running += 1;
|
|
4555
|
+
this.runningByTool.set(toolName, (this.runningByTool.get(toolName) ?? 0) + 1);
|
|
4556
|
+
let released = false;
|
|
4557
|
+
return () => {
|
|
4558
|
+
if (released) return;
|
|
4559
|
+
released = true;
|
|
4560
|
+
this.running -= 1;
|
|
4561
|
+
const count = (this.runningByTool.get(toolName) ?? 1) - 1;
|
|
4562
|
+
if (count <= 0) this.runningByTool.delete(toolName);
|
|
4563
|
+
else this.runningByTool.set(toolName, count);
|
|
4564
|
+
this.pumpWaiters();
|
|
4565
|
+
};
|
|
4566
|
+
}
|
|
4567
|
+
pumpWaiters() {
|
|
4568
|
+
for (let index = 0; index < this.waiters.length; index += 1) {
|
|
4569
|
+
const waiter = this.waiters[index];
|
|
4570
|
+
const limit = this.perToolMaxConcurrent[waiter.toolName] ?? this.maxConcurrent;
|
|
4571
|
+
if (this.running >= this.maxConcurrent || (this.runningByTool.get(waiter.toolName) ?? 0) >= limit) continue;
|
|
4572
|
+
this.waiters.splice(index, 1);
|
|
4573
|
+
waiter.signal.removeEventListener("abort", waiter.onAbort);
|
|
4574
|
+
waiter.resolve(this.reserve(waiter.toolName));
|
|
4575
|
+
index -= 1;
|
|
4576
|
+
}
|
|
4577
|
+
}
|
|
4578
|
+
};
|
|
4579
|
+
|
|
4580
|
+
// src/runtime/agent-run.ts
|
|
4581
|
+
var AGENT_RUN_CONSTRUCTION = /* @__PURE__ */ Symbol("rowan.agentRun.construction");
|
|
4582
|
+
var CREATE_AGENT_RUN = /* @__PURE__ */ Symbol("rowan.agentRun.create");
|
|
4583
|
+
var AgentRun = class _AgentRun {
|
|
4584
|
+
constructor(token, host, initial, publicMessageId) {
|
|
4585
|
+
this.host = host;
|
|
4586
|
+
this.publicMessageId = publicMessageId;
|
|
4587
|
+
if (token !== AGENT_RUN_CONSTRUCTION) {
|
|
4588
|
+
throw new Error("AgentRun lifecycle is owned by AgentRuntime.");
|
|
4589
|
+
}
|
|
4590
|
+
this.current = structuredClone(initial);
|
|
4591
|
+
host.register(this);
|
|
4592
|
+
}
|
|
4593
|
+
host;
|
|
4594
|
+
publicMessageId;
|
|
4595
|
+
current;
|
|
4596
|
+
listeners = /* @__PURE__ */ new Set();
|
|
4597
|
+
static [CREATE_AGENT_RUN](host, initial, publicMessageId) {
|
|
4598
|
+
return new _AgentRun(AGENT_RUN_CONSTRUCTION, host, initial, publicMessageId);
|
|
4599
|
+
}
|
|
4600
|
+
get id() {
|
|
4601
|
+
return this.current.id;
|
|
4602
|
+
}
|
|
4603
|
+
get messageId() {
|
|
4604
|
+
return this.publicMessageId;
|
|
4605
|
+
}
|
|
4606
|
+
get status() {
|
|
4607
|
+
return this.current.state;
|
|
4608
|
+
}
|
|
4609
|
+
get state() {
|
|
4610
|
+
return this.current.state;
|
|
4611
|
+
}
|
|
4612
|
+
get inputRequest() {
|
|
4613
|
+
return this.current.inputRequest ? structuredClone(this.current.inputRequest) : void 0;
|
|
4614
|
+
}
|
|
4615
|
+
async getStatus() {
|
|
4616
|
+
const run = await this.host.getRun(this.id);
|
|
4617
|
+
if (run) this.notify(run);
|
|
4618
|
+
return this.current.state;
|
|
4619
|
+
}
|
|
4620
|
+
subscribe(listener) {
|
|
4621
|
+
this.listeners.add(listener);
|
|
4622
|
+
return () => this.listeners.delete(listener);
|
|
4623
|
+
}
|
|
4624
|
+
consumeRuntimeEvents(consumerId, listener) {
|
|
4625
|
+
return this.host.consumeEvents(consumerId, (event) => {
|
|
4626
|
+
if (event.runId === this.id) return listener(event);
|
|
4627
|
+
});
|
|
4628
|
+
}
|
|
4629
|
+
async result() {
|
|
4630
|
+
while (true) {
|
|
4631
|
+
const run = await this.host.getRun(this.id);
|
|
4632
|
+
if (!run) throw new Error(`Agent Run not found: ${this.id}.`);
|
|
4633
|
+
this.notify(run);
|
|
4634
|
+
if (["completed", "failed", "cancelled"].includes(run.state)) {
|
|
4635
|
+
if (!run.outcome) {
|
|
4636
|
+
throw new Error(`Agent Run ${this.id} reached ${run.state} without an Outcome.`);
|
|
4637
|
+
}
|
|
4638
|
+
return structuredClone(run.outcome);
|
|
4639
|
+
}
|
|
4640
|
+
await this.host.waitForRunChange(this.id, run.state, run.updatedAt);
|
|
4641
|
+
}
|
|
4642
|
+
}
|
|
4643
|
+
async abort(reason = "Agent Run aborted by caller.") {
|
|
4644
|
+
await this.host.abortRun(this.id, reason);
|
|
4645
|
+
}
|
|
4646
|
+
notify(run) {
|
|
4647
|
+
this.current = structuredClone(run);
|
|
4648
|
+
for (const listener of this.listeners) {
|
|
4649
|
+
try {
|
|
4650
|
+
const result = listener(run.state);
|
|
4651
|
+
if (result && typeof result.then === "function") {
|
|
4652
|
+
void Promise.resolve(result).catch(() => void 0);
|
|
4653
|
+
}
|
|
4654
|
+
} catch {
|
|
4655
|
+
}
|
|
4656
|
+
}
|
|
4657
|
+
}
|
|
4658
|
+
};
|
|
4659
|
+
function createAgentRun(host, initial, publicMessageId = initial.messageId) {
|
|
4660
|
+
return AgentRun[CREATE_AGENT_RUN](host, initial, publicMessageId);
|
|
4661
|
+
}
|
|
4662
|
+
|
|
4663
|
+
// src/runtime/agent-runtime.ts
|
|
4664
|
+
var InfrastructureFailureError = class extends Error {
|
|
4665
|
+
constructor(message, options) {
|
|
4666
|
+
super(message, options);
|
|
4667
|
+
this.name = "InfrastructureFailureError";
|
|
4668
|
+
}
|
|
4669
|
+
};
|
|
4670
|
+
var activeRuntime;
|
|
4671
|
+
var AgentRuntime = class _AgentRuntime {
|
|
4672
|
+
stateStore;
|
|
4673
|
+
sessionProvider;
|
|
4674
|
+
maxConcurrentRuns;
|
|
4675
|
+
maxInfrastructureAttempts;
|
|
4676
|
+
leaseDurationMs;
|
|
4677
|
+
leaseRenewalIntervalMs;
|
|
4678
|
+
toolRuntime;
|
|
4679
|
+
bindings = /* @__PURE__ */ new Map();
|
|
4680
|
+
agents = /* @__PURE__ */ new Map();
|
|
4681
|
+
runHandles = /* @__PURE__ */ new Map();
|
|
4682
|
+
runWaiters = /* @__PURE__ */ new Map();
|
|
4683
|
+
pendingRuns = [];
|
|
4684
|
+
scheduledRunIds = /* @__PURE__ */ new Set();
|
|
4685
|
+
runningAgents = /* @__PURE__ */ new Set();
|
|
4686
|
+
pausedAgents = /* @__PURE__ */ new Set();
|
|
4687
|
+
eventSubscriptions = /* @__PURE__ */ new Map();
|
|
4688
|
+
activeDispatches = /* @__PURE__ */ new Set();
|
|
4689
|
+
runningRuns = 0;
|
|
4690
|
+
pumping = false;
|
|
4691
|
+
nextWorkerId = 0;
|
|
4692
|
+
stopped = false;
|
|
4693
|
+
constructor(options) {
|
|
4694
|
+
this.stateStore = options.stateStore;
|
|
4695
|
+
this.sessionProvider = options.sessionProvider;
|
|
4696
|
+
this.maxConcurrentRuns = options.maxConcurrentRuns ?? Number.POSITIVE_INFINITY;
|
|
4697
|
+
this.maxInfrastructureAttempts = options.maxInfrastructureAttempts ?? 3;
|
|
4698
|
+
this.leaseDurationMs = options.leaseDurationMs ?? 6e4;
|
|
4699
|
+
this.leaseRenewalIntervalMs = options.leaseRenewalIntervalMs ?? Math.max(1, Math.floor(this.leaseDurationMs / 2));
|
|
4700
|
+
this.toolRuntime = new ToolRuntime(options.stateStore, options.toolPolicy, () => this.publishEvents());
|
|
4701
|
+
}
|
|
4702
|
+
static async start(options) {
|
|
4703
|
+
if (activeRuntime) {
|
|
4704
|
+
throw new Error("Agent Runtime is already started in this process.");
|
|
4705
|
+
}
|
|
4706
|
+
if (!options.stateStore) {
|
|
4707
|
+
throw new Error("Agent Runtime requires a RuntimeStateStore.");
|
|
4708
|
+
}
|
|
4709
|
+
if (!Number.isFinite(options.maxConcurrentRuns ?? 1) || (options.maxConcurrentRuns ?? 1) <= 0) {
|
|
4710
|
+
throw new Error("Agent Runtime maxConcurrentRuns must be a positive finite number.");
|
|
4711
|
+
}
|
|
4712
|
+
if (!Number.isInteger(options.maxInfrastructureAttempts ?? 3) || (options.maxInfrastructureAttempts ?? 3) <= 0) {
|
|
4713
|
+
throw new Error("Agent Runtime maxInfrastructureAttempts must be a positive integer.");
|
|
4714
|
+
}
|
|
4715
|
+
if (!Number.isFinite(options.leaseDurationMs ?? 6e4) || (options.leaseDurationMs ?? 6e4) <= 0) {
|
|
4716
|
+
throw new Error("Agent Runtime leaseDurationMs must be a positive finite number.");
|
|
4717
|
+
}
|
|
4718
|
+
const leaseDurationMs = options.leaseDurationMs ?? 6e4;
|
|
4719
|
+
const renewalIntervalMs = options.leaseRenewalIntervalMs ?? Math.max(1, Math.floor(leaseDurationMs / 2));
|
|
4720
|
+
if (!Number.isFinite(renewalIntervalMs) || renewalIntervalMs <= 0 || renewalIntervalMs >= leaseDurationMs) {
|
|
4721
|
+
throw new Error("Agent Runtime leaseRenewalIntervalMs must be positive and shorter than leaseDurationMs.");
|
|
4722
|
+
}
|
|
4723
|
+
const runtime = new _AgentRuntime(options);
|
|
4724
|
+
activeRuntime = runtime;
|
|
4725
|
+
try {
|
|
4726
|
+
await runtime.recover();
|
|
4727
|
+
return runtime;
|
|
4728
|
+
} catch (error) {
|
|
4729
|
+
await runtime.stop().catch(() => void 0);
|
|
4730
|
+
throw error;
|
|
4731
|
+
}
|
|
4732
|
+
}
|
|
4733
|
+
async createAgent(options) {
|
|
4734
|
+
this.assertRunning();
|
|
4735
|
+
assertAgentOptions(options);
|
|
4736
|
+
const provider = this.sessionProvider;
|
|
4737
|
+
if (!provider) throw new Error("Agent Runtime requires a SessionManager provider to create an Agent.");
|
|
4738
|
+
const manager = await provider.create({
|
|
4739
|
+
systemPrompt: options.context.systemPrompt,
|
|
4740
|
+
input: "",
|
|
4741
|
+
skills: options.context.skills
|
|
4742
|
+
});
|
|
4743
|
+
const record = await this.stateStore.createAgent({
|
|
4744
|
+
sessionId: manager.getSessionId()
|
|
4745
|
+
});
|
|
4746
|
+
this.pausedAgents.delete(record.id);
|
|
4747
|
+
const agent = await this.attachAgent(record, manager, options);
|
|
4748
|
+
this.publishEvents();
|
|
4749
|
+
return agent;
|
|
4750
|
+
}
|
|
4751
|
+
async reconstructAgent(agentId, options) {
|
|
4752
|
+
this.assertRunning();
|
|
4753
|
+
assertAgentOptions(options);
|
|
4754
|
+
const record = await this.stateStore.getAgent(agentId);
|
|
4755
|
+
if (!record) throw new Error(`Agent not found: ${agentId}.`);
|
|
4756
|
+
if (record.state === "paused") this.pausedAgents.add(record.id);
|
|
4757
|
+
else this.pausedAgents.delete(record.id);
|
|
4758
|
+
const provider = this.sessionProvider;
|
|
4759
|
+
if (!provider) throw new Error("Agent Runtime requires a SessionManager provider to reconstruct an Agent.");
|
|
4760
|
+
const manager = await provider.open(record.sessionId);
|
|
4761
|
+
if (!manager) throw new Error(`Session not found: ${record.sessionId}.`);
|
|
4762
|
+
const agent = await this.attachAgent(record, manager, options);
|
|
4763
|
+
this.publishEvents();
|
|
4764
|
+
return agent;
|
|
4765
|
+
}
|
|
4766
|
+
async stop() {
|
|
4767
|
+
if (this.stopped) return;
|
|
4768
|
+
this.stopped = true;
|
|
4769
|
+
let suspendedAgents;
|
|
4770
|
+
try {
|
|
4771
|
+
suspendedAgents = new Set(
|
|
4772
|
+
(await this.stateStore.listRuns({ states: ["suspended"] })).map((run) => run.agentId)
|
|
4773
|
+
);
|
|
4774
|
+
} catch {
|
|
4775
|
+
suspendedAgents = /* @__PURE__ */ new Set();
|
|
4776
|
+
}
|
|
4777
|
+
for (const [agentId, binding] of this.bindings) {
|
|
4778
|
+
if (!suspendedAgents.has(agentId) && !binding.isSuspended()) binding.abort("Agent Runtime stopped.");
|
|
4779
|
+
}
|
|
4780
|
+
this.bindings.clear();
|
|
4781
|
+
this.agents.clear();
|
|
4782
|
+
this.pendingRuns.length = 0;
|
|
4783
|
+
this.scheduledRunIds.clear();
|
|
4784
|
+
this.pausedAgents.clear();
|
|
4785
|
+
const activeDispatches = [...this.activeDispatches];
|
|
4786
|
+
this.runHandles.clear();
|
|
4787
|
+
for (const waiters of this.runWaiters.values()) {
|
|
4788
|
+
for (const resolve7 of waiters) resolve7();
|
|
4789
|
+
}
|
|
4790
|
+
this.runWaiters.clear();
|
|
4791
|
+
this.eventSubscriptions.clear();
|
|
4792
|
+
await Promise.allSettled(activeDispatches);
|
|
4793
|
+
if (activeRuntime === this) activeRuntime = void 0;
|
|
4794
|
+
}
|
|
4795
|
+
bindAgent(agentId, agent) {
|
|
4796
|
+
this.assertRunning();
|
|
4797
|
+
if (this.bindings.has(agentId)) {
|
|
4798
|
+
throw new Error(`Agent ${agentId} is already bound to a live Agent.`);
|
|
4799
|
+
}
|
|
4800
|
+
this.bindings.set(agentId, agent);
|
|
4801
|
+
void this.pump();
|
|
4802
|
+
if (!this.pausedAgents.has(agentId)) void this.scheduleQueuedRuns(agentId);
|
|
4803
|
+
}
|
|
4804
|
+
registerRunHandle(runId, handle) {
|
|
4805
|
+
this.runHandles.set(runId, handle);
|
|
4806
|
+
}
|
|
4807
|
+
waitForRunChange(runId, state, updatedAt) {
|
|
4808
|
+
return new Promise((resolve7) => {
|
|
4809
|
+
const waiters = this.runWaiters.get(runId) ?? /* @__PURE__ */ new Set();
|
|
4810
|
+
let settled = false;
|
|
4811
|
+
const finish = () => {
|
|
4812
|
+
if (settled) return;
|
|
4813
|
+
settled = true;
|
|
4814
|
+
waiters.delete(finish);
|
|
4815
|
+
if (waiters.size === 0) this.runWaiters.delete(runId);
|
|
4816
|
+
resolve7();
|
|
4817
|
+
};
|
|
4818
|
+
waiters.add(finish);
|
|
4819
|
+
this.runWaiters.set(runId, waiters);
|
|
4820
|
+
void this.stateStore.getRun(runId).then((current) => {
|
|
4821
|
+
if (!current || current.state !== state || current.updatedAt !== updatedAt) finish();
|
|
4822
|
+
}, finish);
|
|
4823
|
+
});
|
|
4824
|
+
}
|
|
4825
|
+
scheduleRun(agentId, runId) {
|
|
4826
|
+
this.assertRunning();
|
|
4827
|
+
if (this.scheduledRunIds.has(runId)) return;
|
|
4828
|
+
this.scheduledRunIds.add(runId);
|
|
4829
|
+
this.pendingRuns.push({ agentId, runId });
|
|
4830
|
+
void this.pump();
|
|
4831
|
+
}
|
|
4832
|
+
async pauseAgent(agentId) {
|
|
4833
|
+
this.assertRunning();
|
|
4834
|
+
this.pausedAgents.add(agentId);
|
|
4835
|
+
try {
|
|
4836
|
+
await this.stateStore.setAgentState(agentId, "paused");
|
|
4837
|
+
} catch (error) {
|
|
4838
|
+
this.pausedAgents.delete(agentId);
|
|
4839
|
+
throw error;
|
|
4840
|
+
}
|
|
4841
|
+
this.publishEvents();
|
|
4842
|
+
}
|
|
4843
|
+
async resumeAgent(agentId) {
|
|
4844
|
+
this.assertRunning();
|
|
4845
|
+
await this.stateStore.setAgentState(agentId, "active");
|
|
4846
|
+
this.pausedAgents.delete(agentId);
|
|
4847
|
+
await this.scheduleQueuedRuns(agentId);
|
|
4848
|
+
void this.pump();
|
|
4849
|
+
this.publishEvents();
|
|
4850
|
+
}
|
|
4851
|
+
async getRun(runId) {
|
|
4852
|
+
return this.stateStore.getRun(runId);
|
|
4853
|
+
}
|
|
4854
|
+
async listActiveRuns() {
|
|
4855
|
+
return this.stateStore.listActiveRuns();
|
|
4856
|
+
}
|
|
4857
|
+
async listRuns(input) {
|
|
4858
|
+
return this.stateStore.listRuns(input);
|
|
4859
|
+
}
|
|
4860
|
+
getAgent(agentId) {
|
|
4861
|
+
return this.agents.get(agentId);
|
|
4862
|
+
}
|
|
4863
|
+
async getMessage(messageId) {
|
|
4864
|
+
return this.stateStore.getMessage(messageId);
|
|
4865
|
+
}
|
|
4866
|
+
async getToolCall(toolCallId) {
|
|
4867
|
+
return this.stateStore.getToolCall(toolCallId);
|
|
4868
|
+
}
|
|
4869
|
+
async abortRun(runId, reason = "Agent Run aborted by caller.") {
|
|
4870
|
+
const run = await this.stateStore.getRun(runId);
|
|
4871
|
+
if (!run || ["completed", "failed", "cancelled"].includes(run.state)) return;
|
|
4872
|
+
const aborted = await this.stateStore.abortRun({
|
|
4873
|
+
runId,
|
|
4874
|
+
outcome: { id: createId("out"), message: reason }
|
|
4875
|
+
});
|
|
4876
|
+
this.notifyRun(aborted);
|
|
4877
|
+
if (run.state === "queued") {
|
|
4878
|
+
const pendingIndex = this.pendingRuns.findIndex((job) => job.runId === runId);
|
|
4879
|
+
if (pendingIndex >= 0) this.pendingRuns.splice(pendingIndex, 1);
|
|
4880
|
+
this.scheduledRunIds.delete(runId);
|
|
4881
|
+
}
|
|
4882
|
+
this.toolRuntime.abortRun(runId);
|
|
4883
|
+
if (run.state === "running" || run.state === "suspended") {
|
|
4884
|
+
this.bindings.get(aborted.agentId)?.abort(reason);
|
|
4885
|
+
}
|
|
4886
|
+
this.publishEvents();
|
|
4887
|
+
}
|
|
4888
|
+
consumeEvents(consumerId, listener) {
|
|
4889
|
+
const subscription = this.registerEventConsumer(consumerId, listener);
|
|
4890
|
+
void subscription.caughtUp.catch(() => void 0);
|
|
4891
|
+
return subscription;
|
|
4892
|
+
}
|
|
4893
|
+
registerEventConsumer(consumerId, listener) {
|
|
4894
|
+
this.assertRunning();
|
|
4895
|
+
if (consumerId.trim().length === 0) {
|
|
4896
|
+
throw new Error("Runtime Event Consumer ID must not be empty.");
|
|
4897
|
+
}
|
|
4898
|
+
if (this.eventSubscriptions.has(consumerId)) {
|
|
4899
|
+
throw new Error(`Runtime Event Consumer is already active: ${consumerId}.`);
|
|
4900
|
+
}
|
|
4901
|
+
const subscription = {
|
|
4902
|
+
consumerId,
|
|
4903
|
+
listener,
|
|
4904
|
+
delivering: false,
|
|
4905
|
+
redeliver: false
|
|
4906
|
+
};
|
|
4907
|
+
this.eventSubscriptions.set(consumerId, subscription);
|
|
4908
|
+
let caughtUpSettled = false;
|
|
4909
|
+
let resolveCaughtUp;
|
|
4910
|
+
let rejectCaughtUp;
|
|
4911
|
+
const caughtUp = new Promise((resolve7, reject) => {
|
|
4912
|
+
resolveCaughtUp = resolve7;
|
|
4913
|
+
rejectCaughtUp = reject;
|
|
4914
|
+
});
|
|
4915
|
+
const stop = () => {
|
|
4916
|
+
if (this.eventSubscriptions.get(consumerId) === subscription) {
|
|
4917
|
+
this.eventSubscriptions.delete(consumerId);
|
|
4918
|
+
}
|
|
4919
|
+
if (!caughtUpSettled) {
|
|
4920
|
+
caughtUpSettled = true;
|
|
4921
|
+
const error = new Error("Runtime Event Consumer stopped before catch-up completed.");
|
|
4922
|
+
error.name = "AbortError";
|
|
4923
|
+
rejectCaughtUp(error);
|
|
4924
|
+
}
|
|
4925
|
+
};
|
|
4926
|
+
void this.deliverEvents(subscription).then(
|
|
4927
|
+
() => {
|
|
4928
|
+
if (!caughtUpSettled) {
|
|
4929
|
+
caughtUpSettled = true;
|
|
4930
|
+
resolveCaughtUp();
|
|
4931
|
+
}
|
|
4932
|
+
},
|
|
4933
|
+
(error) => {
|
|
4934
|
+
if (!caughtUpSettled) {
|
|
4935
|
+
caughtUpSettled = true;
|
|
4936
|
+
rejectCaughtUp(error);
|
|
4937
|
+
}
|
|
4938
|
+
}
|
|
4939
|
+
);
|
|
4940
|
+
return { caughtUp, stop };
|
|
4941
|
+
}
|
|
4942
|
+
async listEvents(cursor) {
|
|
4943
|
+
return this.stateStore.listEvents(cursor);
|
|
4944
|
+
}
|
|
4945
|
+
async recover() {
|
|
4946
|
+
await this.stateStore.recoverLeases();
|
|
4947
|
+
const agents = await this.stateStore.listAgents();
|
|
4948
|
+
for (const record of agents) {
|
|
4949
|
+
if (record.state === "paused") this.pausedAgents.add(record.id);
|
|
4950
|
+
}
|
|
4951
|
+
this.publishEvents();
|
|
4952
|
+
}
|
|
4953
|
+
async scheduleQueuedRuns(agentId) {
|
|
4954
|
+
if (!this.bindings.has(agentId)) return;
|
|
4955
|
+
const runs = await this.stateStore.listRuns({ agentId, states: ["queued"] });
|
|
4956
|
+
for (const run of runs) this.scheduleRun(agentId, run.id);
|
|
4957
|
+
}
|
|
4958
|
+
async attachAgent(record, manager, options) {
|
|
4959
|
+
const model = { provider: options.model.provider, id: options.model.id };
|
|
4960
|
+
const resolvedOptions = {
|
|
4961
|
+
...options,
|
|
4962
|
+
model,
|
|
4963
|
+
stream: options.stream ?? createModelStream(options.model)
|
|
4964
|
+
};
|
|
4965
|
+
const attached = await attachAgent({
|
|
4966
|
+
options: resolvedOptions,
|
|
4967
|
+
agentId: record.id,
|
|
4968
|
+
sessionId: record.sessionId,
|
|
4969
|
+
manager,
|
|
4970
|
+
submit: (message, persist) => this.submitAgentInput(record.id, message, persist),
|
|
4971
|
+
executeTool: (input) => this.toolRuntime.execute(input)
|
|
4972
|
+
});
|
|
4973
|
+
this.bindAgent(record.id, attached.binding);
|
|
4974
|
+
this.agents.set(record.id, attached.agent);
|
|
4975
|
+
return attached.agent;
|
|
4976
|
+
}
|
|
4977
|
+
async submitAgentInput(agentId, input, persist) {
|
|
4978
|
+
this.assertRunning();
|
|
4979
|
+
if (!this.bindings.has(agentId)) throw new Error(`Agent ${agentId} has no live Agent Binding.`);
|
|
4980
|
+
const enqueued = await this.stateStore.enqueueAgentInput({ agentId, input });
|
|
4981
|
+
await persist();
|
|
4982
|
+
if (enqueued.resumed) this.notifyRun(enqueued.run);
|
|
4983
|
+
const run = createAgentRun({
|
|
4984
|
+
register: (handle) => this.registerRunHandle(enqueued.run.id, handle),
|
|
4985
|
+
getRun: (runId) => this.stateStore.getRun(runId),
|
|
4986
|
+
waitForRunChange: (runId, state, updatedAt) => this.waitForRunChange(runId, state, updatedAt),
|
|
4987
|
+
abortRun: (runId, reason) => this.abortRun(runId, reason),
|
|
4988
|
+
consumeEvents: (consumerId, listener) => this.consumeEvents(consumerId, listener)
|
|
4989
|
+
}, enqueued.run, input.id);
|
|
4990
|
+
this.scheduleRun(agentId, enqueued.run.id);
|
|
4991
|
+
this.publishEvents();
|
|
4992
|
+
return run;
|
|
4993
|
+
}
|
|
4994
|
+
async pump() {
|
|
4995
|
+
if (this.pumping || this.stopped) return;
|
|
4996
|
+
this.pumping = true;
|
|
4997
|
+
try {
|
|
4998
|
+
while (this.runningRuns < this.maxConcurrentRuns) {
|
|
4999
|
+
const index = this.pendingRuns.findIndex((job2) => this.bindings.has(job2.agentId) && !this.pausedAgents.has(job2.agentId) && !this.runningAgents.has(job2.agentId));
|
|
5000
|
+
if (index < 0) return;
|
|
5001
|
+
const [job] = this.pendingRuns.splice(index, 1);
|
|
5002
|
+
if (!job) return;
|
|
5003
|
+
this.runningRuns += 1;
|
|
5004
|
+
this.runningAgents.add(job.agentId);
|
|
5005
|
+
let dispatch;
|
|
5006
|
+
dispatch = this.dispatchRun(job).finally(() => {
|
|
5007
|
+
this.runningRuns -= 1;
|
|
5008
|
+
this.runningAgents.delete(job.agentId);
|
|
5009
|
+
this.scheduledRunIds.delete(job.runId);
|
|
5010
|
+
this.activeDispatches.delete(dispatch);
|
|
5011
|
+
void this.pump();
|
|
5012
|
+
});
|
|
5013
|
+
this.activeDispatches.add(dispatch);
|
|
5014
|
+
void dispatch;
|
|
5015
|
+
}
|
|
5016
|
+
} finally {
|
|
5017
|
+
this.pumping = false;
|
|
5018
|
+
}
|
|
5019
|
+
}
|
|
5020
|
+
async dispatchRun(job) {
|
|
5021
|
+
const binding = this.bindings.get(job.agentId);
|
|
5022
|
+
if (!binding) return;
|
|
5023
|
+
const workerId = `runtime-worker-${++this.nextWorkerId}`;
|
|
5024
|
+
let suspendedResolve;
|
|
5025
|
+
let suspended = false;
|
|
5026
|
+
const suspendedSignal = new Promise((resolve7) => {
|
|
5027
|
+
suspendedResolve = resolve7;
|
|
5028
|
+
});
|
|
5029
|
+
const control = {
|
|
5030
|
+
suspend: async (reason, executionState, inputRequest) => {
|
|
5031
|
+
if (suspended) return;
|
|
5032
|
+
suspended = true;
|
|
5033
|
+
const current = await this.stateStore.suspendRun({ runId: job.runId, reason, executionState, inputRequest });
|
|
5034
|
+
this.notifyRun(current);
|
|
5035
|
+
this.publishEvents();
|
|
5036
|
+
suspendedResolve?.();
|
|
5037
|
+
}
|
|
5038
|
+
};
|
|
5039
|
+
let renewalTimer;
|
|
5040
|
+
let renewalFailed = false;
|
|
5041
|
+
let failRenewal;
|
|
5042
|
+
const renewalFailureSignal = new Promise((resolve7) => {
|
|
5043
|
+
failRenewal = resolve7;
|
|
5044
|
+
});
|
|
5045
|
+
try {
|
|
5046
|
+
const leased = await this.stateStore.leaseRun({
|
|
5047
|
+
runId: job.runId,
|
|
5048
|
+
workerId,
|
|
5049
|
+
leaseDurationMs: this.leaseDurationMs
|
|
5050
|
+
});
|
|
5051
|
+
this.notifyRun(leased.run);
|
|
5052
|
+
try {
|
|
5053
|
+
await binding.persistInput(leased.message.input);
|
|
5054
|
+
} catch (error) {
|
|
5055
|
+
throw new InfrastructureFailureError("Agent Input persistence failed.", { cause: error });
|
|
5056
|
+
}
|
|
5057
|
+
renewalTimer = setInterval(() => {
|
|
5058
|
+
void this.stateStore.renewLease({
|
|
5059
|
+
runId: job.runId,
|
|
5060
|
+
leaseId: leased.lease.id,
|
|
5061
|
+
leaseDurationMs: this.leaseDurationMs
|
|
5062
|
+
}).catch((error) => {
|
|
5063
|
+
if (renewalFailed) return;
|
|
5064
|
+
renewalFailed = true;
|
|
5065
|
+
const failure = new InfrastructureFailureError("Agent Run Lease renewal failed.", { cause: error });
|
|
5066
|
+
binding.abort(failure.message);
|
|
5067
|
+
failRenewal(failure);
|
|
5068
|
+
});
|
|
5069
|
+
}, this.leaseRenewalIntervalMs);
|
|
5070
|
+
const execution = binding.execute(leased.message.input, job.runId, control, leased.run.executionState);
|
|
5071
|
+
const outcome = await Promise.race([
|
|
5072
|
+
execution.then((value) => ({ kind: "terminal", value })).catch((error) => ({ kind: "error", error })),
|
|
5073
|
+
suspendedSignal.then(() => ({ kind: "suspended" })),
|
|
5074
|
+
renewalFailureSignal.then((error) => ({ kind: "renewal_failure", error }))
|
|
5075
|
+
]);
|
|
5076
|
+
if (outcome.kind === "suspended") {
|
|
5077
|
+
void execution.catch(() => void 0);
|
|
5078
|
+
return;
|
|
5079
|
+
}
|
|
5080
|
+
if (outcome.kind === "error") {
|
|
5081
|
+
await this.finishFailedRun(job, outcome.error);
|
|
5082
|
+
return;
|
|
5083
|
+
}
|
|
5084
|
+
if (outcome.kind === "renewal_failure") {
|
|
5085
|
+
await execution.catch(() => void 0);
|
|
5086
|
+
await this.finishFailedRun(job, outcome.error);
|
|
5087
|
+
return;
|
|
5088
|
+
}
|
|
5089
|
+
await this.finishRun(job, outcome.value);
|
|
5090
|
+
} catch (error) {
|
|
5091
|
+
const current = await this.stateStore.getRun(job.runId);
|
|
5092
|
+
if (!current || ["completed", "failed", "cancelled", "suspended"].includes(current.state)) {
|
|
5093
|
+
if (current) this.notifyRun(current);
|
|
5094
|
+
return;
|
|
5095
|
+
}
|
|
5096
|
+
if (current.state !== "running") {
|
|
5097
|
+
this.notifyRun(current);
|
|
5098
|
+
return;
|
|
5099
|
+
}
|
|
5100
|
+
await this.finishFailedRun(job, error);
|
|
5101
|
+
} finally {
|
|
5102
|
+
if (renewalTimer) clearInterval(renewalTimer);
|
|
5103
|
+
}
|
|
5104
|
+
}
|
|
5105
|
+
async finishRun(job, outcome) {
|
|
5106
|
+
const current = await this.stateStore.getRun(job.runId);
|
|
5107
|
+
if (!current || ["completed", "failed", "cancelled"].includes(current.state)) {
|
|
5108
|
+
if (current) this.notifyRun(current);
|
|
5109
|
+
return;
|
|
5110
|
+
}
|
|
5111
|
+
const completed = await this.stateStore.completeRun({ runId: current.id, outcome });
|
|
5112
|
+
this.notifyRun(completed);
|
|
5113
|
+
this.publishEvents();
|
|
5114
|
+
}
|
|
5115
|
+
async finishFailedRun(job, error) {
|
|
5116
|
+
const current = await this.stateStore.getRun(job.runId);
|
|
5117
|
+
if (!current || ["completed", "failed", "cancelled"].includes(current.state)) {
|
|
5118
|
+
if (current) this.notifyRun(current);
|
|
5119
|
+
return;
|
|
5120
|
+
}
|
|
5121
|
+
const message = error instanceof Error ? error.message : "Agent Run failed.";
|
|
5122
|
+
const outcome = { id: createId("out"), message };
|
|
5123
|
+
const retryable = isRetryableInfrastructureError(error);
|
|
5124
|
+
if (retryable && current.attempt < this.maxInfrastructureAttempts) {
|
|
5125
|
+
const retried = await this.stateStore.retryRun({ runId: current.id, reason: message });
|
|
5126
|
+
this.notifyRun(retried);
|
|
5127
|
+
this.pendingRuns.push(job);
|
|
5128
|
+
this.publishEvents();
|
|
5129
|
+
return;
|
|
5130
|
+
}
|
|
5131
|
+
const failed2 = retryable ? await this.stateStore.exhaustRun({ runId: current.id, outcome, reason: message }) : await this.stateStore.completeRun({ runId: current.id, state: "failed", outcome });
|
|
5132
|
+
this.notifyRun(failed2);
|
|
5133
|
+
this.publishEvents();
|
|
5134
|
+
}
|
|
5135
|
+
notifyRun(run) {
|
|
5136
|
+
this.runHandles.get(run.id)?.notify(run);
|
|
5137
|
+
const waiters = this.runWaiters.get(run.id);
|
|
5138
|
+
if (!waiters) return;
|
|
5139
|
+
this.runWaiters.delete(run.id);
|
|
5140
|
+
for (const resolve7 of waiters) resolve7();
|
|
5141
|
+
}
|
|
5142
|
+
publishEvents() {
|
|
5143
|
+
for (const subscription of this.eventSubscriptions.values()) {
|
|
5144
|
+
void this.deliverEvents(subscription).catch(() => void 0);
|
|
5145
|
+
}
|
|
5146
|
+
}
|
|
5147
|
+
async deliverEvents(subscription) {
|
|
5148
|
+
if (subscription.delivering) {
|
|
5149
|
+
subscription.redeliver = true;
|
|
5150
|
+
return;
|
|
5151
|
+
}
|
|
5152
|
+
subscription.delivering = true;
|
|
5153
|
+
try {
|
|
5154
|
+
do {
|
|
5155
|
+
subscription.redeliver = false;
|
|
5156
|
+
const checkpoint = await this.stateStore.getEventCheckpoint(subscription.consumerId);
|
|
5157
|
+
const events = await this.stateStore.listEvents(
|
|
5158
|
+
checkpoint.eventId ? { after: checkpoint.eventId } : void 0
|
|
5159
|
+
);
|
|
5160
|
+
for (const event of events) {
|
|
5161
|
+
if (this.eventSubscriptions.get(subscription.consumerId) !== subscription) return;
|
|
5162
|
+
const disposition = await subscription.listener(structuredClone(event));
|
|
5163
|
+
if (disposition?.type === "enqueue") {
|
|
5164
|
+
const delivered = await this.stateStore.acknowledgeEventAndEnqueueAgentInput({
|
|
5165
|
+
consumerId: subscription.consumerId,
|
|
5166
|
+
eventId: event.id,
|
|
5167
|
+
agentId: disposition.agentId,
|
|
5168
|
+
input: disposition.input
|
|
5169
|
+
});
|
|
5170
|
+
if (delivered.enqueued) {
|
|
5171
|
+
this.scheduleRun(delivered.enqueued.run.agentId, delivered.enqueued.run.id);
|
|
5172
|
+
this.publishEvents();
|
|
5173
|
+
}
|
|
5174
|
+
} else {
|
|
5175
|
+
await this.stateStore.acknowledgeEvent(subscription.consumerId, event.id);
|
|
5176
|
+
}
|
|
5177
|
+
}
|
|
5178
|
+
} while (subscription.redeliver);
|
|
5179
|
+
} finally {
|
|
5180
|
+
subscription.delivering = false;
|
|
5181
|
+
}
|
|
5182
|
+
}
|
|
5183
|
+
assertRunning() {
|
|
5184
|
+
if (this.stopped || activeRuntime !== this) throw new Error("Agent Runtime is stopped.");
|
|
5185
|
+
}
|
|
5186
|
+
};
|
|
5187
|
+
function isModelConfig(value) {
|
|
5188
|
+
if (typeof value !== "object" || value === null) return false;
|
|
5189
|
+
return "protocol" in value && "baseUrl" in value && "apiKey" in value;
|
|
5190
|
+
}
|
|
5191
|
+
function assertAgentOptions(options) {
|
|
5192
|
+
if (options.stream && isModelConfig(options.model)) {
|
|
5193
|
+
throw new Error("AgentOptions accepts either a complete model config or a custom stream, not both.");
|
|
5194
|
+
}
|
|
5195
|
+
}
|
|
5196
|
+
function isRetryableInfrastructureError(error) {
|
|
5197
|
+
if (error instanceof InfrastructureFailureError) return true;
|
|
5198
|
+
return typeof error === "object" && error !== null && "retryable" in error && error.retryable === true;
|
|
5199
|
+
}
|
|
5200
|
+
|
|
5201
|
+
// src/runtime/domain.ts
|
|
5202
|
+
function runEventPayload(metadata, payload = {}) {
|
|
5203
|
+
return metadata ? { ...payload, metadata: structuredClone(metadata) } : payload;
|
|
5204
|
+
}
|
|
5205
|
+
|
|
5206
|
+
// src/runtime/memory-store.ts
|
|
5207
|
+
function clone2(value) {
|
|
5208
|
+
return structuredClone(value);
|
|
5209
|
+
}
|
|
5210
|
+
function transitionError(entity, id, state, action) {
|
|
5211
|
+
return new Error(`Cannot ${action} ${entity} ${id} from state "${state}".`);
|
|
5212
|
+
}
|
|
5213
|
+
function requireTransition(entity, id, state, action, allowedStates) {
|
|
5214
|
+
if (!allowedStates.includes(state)) {
|
|
5215
|
+
throw transitionError(entity, id, state, action);
|
|
5216
|
+
}
|
|
5217
|
+
}
|
|
5218
|
+
function now(input) {
|
|
5219
|
+
const date = input ? new Date(input) : /* @__PURE__ */ new Date();
|
|
5220
|
+
return { date, timestamp: createTimestamp(date) };
|
|
5221
|
+
}
|
|
5222
|
+
var InMemoryRuntimeStateStore = class {
|
|
5223
|
+
agents = /* @__PURE__ */ new Map();
|
|
5224
|
+
messages = /* @__PURE__ */ new Map();
|
|
5225
|
+
runs = /* @__PURE__ */ new Map();
|
|
5226
|
+
toolCalls = /* @__PURE__ */ new Map();
|
|
5227
|
+
events = [];
|
|
5228
|
+
eventCheckpoints = /* @__PURE__ */ new Map();
|
|
5229
|
+
async createAgent(input) {
|
|
5230
|
+
const { timestamp } = now();
|
|
5231
|
+
const agent = {
|
|
5232
|
+
id: createId("agt"),
|
|
5233
|
+
sessionId: input.sessionId,
|
|
5234
|
+
state: "active",
|
|
5235
|
+
createdAt: timestamp,
|
|
5236
|
+
updatedAt: timestamp
|
|
5237
|
+
};
|
|
5238
|
+
this.agents.set(agent.id, agent);
|
|
5239
|
+
this.recordEvent("agent_created", { agentId: agent.id });
|
|
5240
|
+
return clone2(agent);
|
|
5241
|
+
}
|
|
5242
|
+
async getAgent(agentId) {
|
|
5243
|
+
const agent = this.agents.get(agentId);
|
|
5244
|
+
return agent ? clone2(agent) : void 0;
|
|
5245
|
+
}
|
|
5246
|
+
async listAgents() {
|
|
5247
|
+
return clone2([...this.agents.values()]);
|
|
5248
|
+
}
|
|
5249
|
+
async setAgentState(agentId, state) {
|
|
5250
|
+
const agent = this.requireAgent(agentId);
|
|
5251
|
+
if (agent.state === state) return clone2(agent);
|
|
5252
|
+
const { timestamp } = now();
|
|
5253
|
+
agent.state = state;
|
|
5254
|
+
agent.updatedAt = timestamp;
|
|
5255
|
+
this.recordEvent(state === "paused" ? "agent_paused" : "agent_resumed", { agentId });
|
|
5256
|
+
return clone2(agent);
|
|
5257
|
+
}
|
|
5258
|
+
async enqueueAgentInput(input) {
|
|
5259
|
+
return this.enqueueAgentInputState(input);
|
|
5260
|
+
}
|
|
5261
|
+
enqueueAgentInputState(input) {
|
|
5262
|
+
this.requireAgent(input.agentId);
|
|
5263
|
+
const suspended = [...this.runs.values()].filter((run2) => run2.agentId === input.agentId && run2.state === "suspended").sort((a, b) => a.createdAt.localeCompare(b.createdAt))[0];
|
|
5264
|
+
const { timestamp } = now();
|
|
5265
|
+
const messageId = createId("rmsg");
|
|
5266
|
+
const runId = suspended?.id ?? createId("run");
|
|
5267
|
+
const message = {
|
|
5268
|
+
id: messageId,
|
|
5269
|
+
agentId: input.agentId,
|
|
5270
|
+
kind: "agent_input",
|
|
5271
|
+
input: clone2(input.input),
|
|
5272
|
+
state: "queued",
|
|
5273
|
+
attempts: 0,
|
|
5274
|
+
runId,
|
|
5275
|
+
createdAt: timestamp,
|
|
5276
|
+
updatedAt: timestamp
|
|
5277
|
+
};
|
|
5278
|
+
if (suspended) {
|
|
5279
|
+
suspended.state = "queued";
|
|
5280
|
+
suspended.messageId = messageId;
|
|
5281
|
+
delete suspended.suspensionReason;
|
|
5282
|
+
delete suspended.inputRequest;
|
|
5283
|
+
suspended.updatedAt = timestamp;
|
|
5284
|
+
message.runId = suspended.id;
|
|
5285
|
+
}
|
|
5286
|
+
const run = {
|
|
5287
|
+
id: runId,
|
|
5288
|
+
agentId: input.agentId,
|
|
5289
|
+
messageId,
|
|
5290
|
+
state: "queued",
|
|
5291
|
+
attempt: 0,
|
|
5292
|
+
...suspended?.metadata ? { metadata: clone2(suspended.metadata) } : input.input.metadata ? { metadata: clone2(input.input.metadata) } : {},
|
|
5293
|
+
createdAt: timestamp,
|
|
5294
|
+
updatedAt: timestamp
|
|
5295
|
+
};
|
|
5296
|
+
this.messages.set(message.id, message);
|
|
5297
|
+
this.runs.set(run.id, suspended ?? run);
|
|
5298
|
+
this.recordEvent("message_enqueued", { agentId: input.agentId, messageId });
|
|
5299
|
+
this.recordEvent("run_enqueued", { agentId: input.agentId, messageId, runId }, runEventPayload(suspended?.metadata ?? input.input.metadata));
|
|
5300
|
+
return { message: clone2(message), run: clone2(suspended ?? run), resumed: Boolean(suspended) };
|
|
5301
|
+
}
|
|
5302
|
+
async getMessage(messageId) {
|
|
5303
|
+
const message = this.messages.get(messageId);
|
|
5304
|
+
return message ? clone2(message) : void 0;
|
|
5305
|
+
}
|
|
5306
|
+
async getRun(runId) {
|
|
5307
|
+
const run = this.runs.get(runId);
|
|
5308
|
+
return run ? clone2(run) : void 0;
|
|
5309
|
+
}
|
|
5310
|
+
async listRuns(input = {}) {
|
|
5311
|
+
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) || String(a.id).localeCompare(String(b.id))));
|
|
5312
|
+
}
|
|
5313
|
+
async listActiveRuns() {
|
|
5314
|
+
return this.listRuns({ states: ["queued", "running", "suspended"] });
|
|
5315
|
+
}
|
|
5316
|
+
async leaseRun(input) {
|
|
5317
|
+
const run = this.requireRun(input.runId);
|
|
5318
|
+
requireTransition("Agent Run", run.id, run.state, "lease", ["queued"]);
|
|
5319
|
+
const message = this.requireMessage(run.messageId);
|
|
5320
|
+
requireTransition("Runtime Message", message.id, message.state, "lease", ["queued"]);
|
|
5321
|
+
const agent = this.requireAgent(run.agentId);
|
|
5322
|
+
if (agent.state !== "active") {
|
|
5323
|
+
throw transitionError("Agent", agent.id, agent.state, "lease a Run for");
|
|
5324
|
+
}
|
|
5325
|
+
if ([...this.runs.values()].some(
|
|
5326
|
+
(candidate) => candidate.agentId === run.agentId && candidate.id !== run.id && (candidate.state === "running" || candidate.state === "suspended")
|
|
5327
|
+
)) {
|
|
5328
|
+
throw new Error(`Agent ${run.agentId} already has an active Run.`);
|
|
5329
|
+
}
|
|
5330
|
+
if (!Number.isFinite(input.leaseDurationMs) || input.leaseDurationMs <= 0) {
|
|
5331
|
+
throw new Error("Lease duration must be a positive finite number.");
|
|
5332
|
+
}
|
|
5333
|
+
const { date, timestamp } = now(input.now);
|
|
5334
|
+
const lease = {
|
|
5335
|
+
id: createId("lease"),
|
|
5336
|
+
runId: run.id,
|
|
5337
|
+
workerId: input.workerId,
|
|
5338
|
+
leasedAt: timestamp,
|
|
5339
|
+
expiresAt: createTimestamp(new Date(date.getTime() + input.leaseDurationMs))
|
|
5340
|
+
};
|
|
5341
|
+
run.state = "running";
|
|
5342
|
+
run.attempt += 1;
|
|
5343
|
+
run.leaseId = lease.id;
|
|
5344
|
+
run.updatedAt = timestamp;
|
|
5345
|
+
message.state = "leased";
|
|
5346
|
+
message.attempts += 1;
|
|
5347
|
+
message.lease = lease;
|
|
5348
|
+
message.updatedAt = timestamp;
|
|
5349
|
+
this.recordEvent("run_leased", { agentId: run.agentId, messageId: message.id, runId: run.id }, lease);
|
|
5350
|
+
return { run: clone2(run), message: clone2(message), lease: clone2(lease) };
|
|
5351
|
+
}
|
|
5352
|
+
async renewLease(input) {
|
|
5353
|
+
const run = this.requireRun(input.runId);
|
|
5354
|
+
requireTransition("Agent Run", run.id, run.state, "renew Lease", ["running"]);
|
|
5355
|
+
const message = this.requireMessage(run.messageId);
|
|
5356
|
+
requireTransition("Runtime Message", message.id, message.state, "renew Lease", ["leased"]);
|
|
5357
|
+
if (run.leaseId !== input.leaseId || message.lease?.id !== input.leaseId) {
|
|
5358
|
+
throw new Error(`Lease ${input.leaseId} does not own Agent Run ${run.id}.`);
|
|
5359
|
+
}
|
|
5360
|
+
if (!Number.isFinite(input.leaseDurationMs) || input.leaseDurationMs <= 0) {
|
|
5361
|
+
throw new Error("Lease duration must be a positive finite number.");
|
|
5362
|
+
}
|
|
5363
|
+
const { date, timestamp } = now(input.now);
|
|
5364
|
+
const lease = {
|
|
5365
|
+
...message.lease,
|
|
5366
|
+
expiresAt: createTimestamp(new Date(date.getTime() + input.leaseDurationMs))
|
|
5367
|
+
};
|
|
5368
|
+
message.lease = lease;
|
|
5369
|
+
message.updatedAt = timestamp;
|
|
5370
|
+
return clone2(lease);
|
|
5371
|
+
}
|
|
5372
|
+
async suspendRun(input) {
|
|
5373
|
+
const run = this.requireRun(input.runId);
|
|
5374
|
+
requireTransition("Agent Run", run.id, run.state, "suspend", ["running"]);
|
|
5375
|
+
const message = this.requireMessage(run.messageId);
|
|
5376
|
+
const { timestamp } = now();
|
|
5377
|
+
run.state = "suspended";
|
|
5378
|
+
run.suspensionReason = input.reason;
|
|
5379
|
+
if (input.inputRequest) run.inputRequest = clone2(input.inputRequest);
|
|
5380
|
+
if (input.executionState) run.executionState = clone2(input.executionState);
|
|
5381
|
+
delete run.leaseId;
|
|
5382
|
+
run.updatedAt = timestamp;
|
|
5383
|
+
requireTransition("Runtime Message", message.id, message.state, "acknowledge", ["leased"]);
|
|
5384
|
+
message.state = "acknowledged";
|
|
5385
|
+
delete message.lease;
|
|
5386
|
+
message.updatedAt = timestamp;
|
|
5387
|
+
this.recordEvent("run_suspended", { agentId: run.agentId, messageId: message.id, runId: run.id }, runEventPayload(run.metadata, {
|
|
5388
|
+
reason: input.reason
|
|
5389
|
+
}));
|
|
5390
|
+
return clone2(run);
|
|
5391
|
+
}
|
|
5392
|
+
async completeRun(input) {
|
|
5393
|
+
const run = this.requireRun(input.runId);
|
|
5394
|
+
requireTransition("Agent Run", run.id, run.state, "complete", ["running"]);
|
|
5395
|
+
const message = this.requireMessage(run.messageId);
|
|
5396
|
+
requireTransition("Runtime Message", message.id, message.state, "acknowledge", ["leased"]);
|
|
5397
|
+
const { timestamp } = now();
|
|
5398
|
+
const state = input.state ?? "completed";
|
|
5399
|
+
run.state = state;
|
|
5400
|
+
run.outcome = clone2(input.outcome);
|
|
5401
|
+
delete run.inputRequest;
|
|
5402
|
+
delete run.executionState;
|
|
5403
|
+
delete run.leaseId;
|
|
5404
|
+
run.updatedAt = timestamp;
|
|
5405
|
+
message.state = "acknowledged";
|
|
5406
|
+
delete message.lease;
|
|
5407
|
+
message.updatedAt = timestamp;
|
|
5408
|
+
this.recordEvent("run_completed", { agentId: run.agentId, messageId: run.messageId, runId: run.id }, runEventPayload(run.metadata, {
|
|
5409
|
+
state,
|
|
5410
|
+
outcome: input.outcome
|
|
5411
|
+
}));
|
|
5412
|
+
this.recordEvent("message_acknowledged", { agentId: message.agentId, messageId: message.id });
|
|
5413
|
+
return clone2(run);
|
|
5414
|
+
}
|
|
5415
|
+
async retryRun(input) {
|
|
5416
|
+
const run = this.requireRun(input.runId);
|
|
5417
|
+
requireTransition("Agent Run", run.id, run.state, "retry", ["running"]);
|
|
5418
|
+
const message = this.requireMessage(run.messageId);
|
|
5419
|
+
requireTransition("Runtime Message", message.id, message.state, "retry", ["leased"]);
|
|
5420
|
+
const { timestamp } = now();
|
|
5421
|
+
run.state = "queued";
|
|
5422
|
+
delete run.leaseId;
|
|
5423
|
+
run.updatedAt = timestamp;
|
|
5424
|
+
message.state = "queued";
|
|
5425
|
+
delete message.lease;
|
|
5426
|
+
message.updatedAt = timestamp;
|
|
5427
|
+
this.recordEvent("run_retry_scheduled", {
|
|
5428
|
+
agentId: run.agentId,
|
|
5429
|
+
messageId: message.id,
|
|
5430
|
+
runId: run.id
|
|
5431
|
+
}, { reason: input.reason, attempt: run.attempt });
|
|
5432
|
+
return clone2(run);
|
|
5433
|
+
}
|
|
5434
|
+
async exhaustRun(input) {
|
|
5435
|
+
const run = this.requireRun(input.runId);
|
|
5436
|
+
requireTransition("Agent Run", run.id, run.state, "exhaust retries", ["running"]);
|
|
5437
|
+
const message = this.requireMessage(run.messageId);
|
|
5438
|
+
requireTransition("Runtime Message", message.id, message.state, "dead-letter", ["leased"]);
|
|
5439
|
+
const { timestamp } = now();
|
|
5440
|
+
run.state = "failed";
|
|
5441
|
+
run.outcome = clone2(input.outcome);
|
|
5442
|
+
delete run.inputRequest;
|
|
5443
|
+
delete run.executionState;
|
|
5444
|
+
delete run.leaseId;
|
|
5445
|
+
run.updatedAt = timestamp;
|
|
5446
|
+
message.state = "dead_lettered";
|
|
5447
|
+
message.deadLetterReason = input.reason;
|
|
5448
|
+
delete message.lease;
|
|
5449
|
+
message.updatedAt = timestamp;
|
|
5450
|
+
this.recordEvent("run_completed", { agentId: run.agentId, messageId: message.id, runId: run.id }, runEventPayload(run.metadata, {
|
|
5451
|
+
state: "failed",
|
|
5452
|
+
outcome: input.outcome
|
|
5453
|
+
}));
|
|
5454
|
+
this.recordEvent("message_dead_lettered", { agentId: run.agentId, messageId: message.id }, {
|
|
5455
|
+
reason: input.reason
|
|
5456
|
+
});
|
|
5457
|
+
return clone2(run);
|
|
5458
|
+
}
|
|
5459
|
+
async abortRun(input) {
|
|
5460
|
+
const run = this.requireRun(input.runId);
|
|
5461
|
+
requireTransition("Agent Run", run.id, run.state, "abort", ["queued", "running", "suspended"]);
|
|
5462
|
+
const message = this.requireMessage(run.messageId);
|
|
5463
|
+
const { timestamp } = now();
|
|
5464
|
+
run.state = "cancelled";
|
|
5465
|
+
run.outcome = clone2(input.outcome);
|
|
5466
|
+
delete run.inputRequest;
|
|
5467
|
+
delete run.executionState;
|
|
5468
|
+
delete run.leaseId;
|
|
5469
|
+
run.updatedAt = timestamp;
|
|
5470
|
+
if (message.state === "queued" || message.state === "leased") {
|
|
5471
|
+
message.state = "acknowledged";
|
|
5472
|
+
delete message.lease;
|
|
5473
|
+
message.updatedAt = timestamp;
|
|
5474
|
+
}
|
|
5475
|
+
this.recordEvent("run_aborted", { agentId: run.agentId, messageId: message.id, runId: run.id }, runEventPayload(run.metadata, {
|
|
5476
|
+
state: "cancelled",
|
|
5477
|
+
outcome: input.outcome
|
|
5478
|
+
}));
|
|
5479
|
+
return clone2(run);
|
|
5480
|
+
}
|
|
5481
|
+
async recoverExpiredLeases(input) {
|
|
5482
|
+
const { date, timestamp } = now(input);
|
|
5483
|
+
const recovered = [];
|
|
5484
|
+
for (const run of this.runs.values()) {
|
|
5485
|
+
if (run.state !== "running" || !run.leaseId) continue;
|
|
5486
|
+
const message = this.requireMessage(run.messageId);
|
|
5487
|
+
if (message.state !== "leased" || !message.lease || Date.parse(message.lease.expiresAt) > date.getTime()) continue;
|
|
5488
|
+
run.state = "queued";
|
|
5489
|
+
delete run.leaseId;
|
|
5490
|
+
run.updatedAt = timestamp;
|
|
5491
|
+
message.state = "queued";
|
|
5492
|
+
delete message.lease;
|
|
5493
|
+
message.updatedAt = timestamp;
|
|
5494
|
+
this.recordEvent("lease_expired", { agentId: run.agentId, messageId: message.id, runId: run.id });
|
|
5495
|
+
recovered.push(clone2(run));
|
|
5496
|
+
}
|
|
5497
|
+
return recovered;
|
|
5498
|
+
}
|
|
5499
|
+
async recoverLeases() {
|
|
5500
|
+
const { timestamp } = now();
|
|
5501
|
+
const recovered = [];
|
|
5502
|
+
for (const run of this.runs.values()) {
|
|
5503
|
+
if (run.state !== "running" || !run.leaseId) continue;
|
|
5504
|
+
const message = this.requireMessage(run.messageId);
|
|
5505
|
+
if (message.state !== "leased" || !message.lease) continue;
|
|
5506
|
+
run.state = "queued";
|
|
5507
|
+
delete run.leaseId;
|
|
5508
|
+
run.updatedAt = timestamp;
|
|
5509
|
+
message.state = "queued";
|
|
5510
|
+
delete message.lease;
|
|
5511
|
+
message.updatedAt = timestamp;
|
|
5512
|
+
this.recordEvent("lease_recovered", { agentId: run.agentId, messageId: message.id, runId: run.id });
|
|
5513
|
+
recovered.push(clone2(run));
|
|
5514
|
+
}
|
|
5515
|
+
return recovered;
|
|
5516
|
+
}
|
|
5517
|
+
async createToolCall(input) {
|
|
5518
|
+
this.requireAgent(input.agentId);
|
|
5519
|
+
this.requireRun(input.runId);
|
|
5520
|
+
const { timestamp } = now();
|
|
5521
|
+
const toolCall = {
|
|
5522
|
+
id: createId("call"),
|
|
5523
|
+
agentId: input.agentId,
|
|
5524
|
+
runId: input.runId,
|
|
5525
|
+
name: input.name,
|
|
5526
|
+
args: clone2(input.args),
|
|
5527
|
+
state: "queued",
|
|
5528
|
+
createdAt: timestamp,
|
|
5529
|
+
updatedAt: timestamp
|
|
5530
|
+
};
|
|
5531
|
+
this.toolCalls.set(toolCall.id, toolCall);
|
|
5532
|
+
this.recordEvent("tool_call_created", { agentId: input.agentId, runId: input.runId, toolCallId: toolCall.id });
|
|
5533
|
+
return clone2(toolCall);
|
|
5534
|
+
}
|
|
5535
|
+
async startToolCall(toolCallId) {
|
|
5536
|
+
const toolCall = this.requireToolCall(toolCallId);
|
|
5537
|
+
requireTransition("Tool Call", toolCall.id, toolCall.state, "start", ["queued"]);
|
|
5538
|
+
const { timestamp } = now();
|
|
5539
|
+
toolCall.state = "running";
|
|
5540
|
+
toolCall.updatedAt = timestamp;
|
|
5541
|
+
this.recordEvent("tool_call_started", { agentId: toolCall.agentId, runId: toolCall.runId, toolCallId: toolCall.id });
|
|
5542
|
+
return clone2(toolCall);
|
|
5543
|
+
}
|
|
5544
|
+
async completeToolCall(input) {
|
|
5545
|
+
const toolCall = this.requireToolCall(input.toolCallId);
|
|
5546
|
+
const state = input.state ?? (input.result.ok ? "completed" : "failed");
|
|
5547
|
+
requireTransition(
|
|
5548
|
+
"Tool Call",
|
|
5549
|
+
toolCall.id,
|
|
5550
|
+
toolCall.state,
|
|
5551
|
+
"complete",
|
|
5552
|
+
state === "failed" ? ["queued", "running"] : ["running"]
|
|
5553
|
+
);
|
|
5554
|
+
const { timestamp } = now();
|
|
5555
|
+
toolCall.state = state;
|
|
5556
|
+
toolCall.result = clone2(input.result);
|
|
5557
|
+
toolCall.updatedAt = timestamp;
|
|
5558
|
+
this.recordEvent(state === "failed" ? "tool_call_failed" : "tool_call_completed", {
|
|
5559
|
+
agentId: toolCall.agentId,
|
|
5560
|
+
runId: toolCall.runId,
|
|
5561
|
+
toolCallId: toolCall.id
|
|
5562
|
+
}, { state: toolCall.state });
|
|
5563
|
+
return clone2(toolCall);
|
|
5564
|
+
}
|
|
5565
|
+
async markToolCallIndeterminate(input) {
|
|
5566
|
+
const toolCall = this.requireToolCall(input.toolCallId);
|
|
5567
|
+
requireTransition("Tool Call", toolCall.id, toolCall.state, "mark indeterminate", ["running"]);
|
|
5568
|
+
const { timestamp } = now();
|
|
5569
|
+
toolCall.state = "indeterminate";
|
|
5570
|
+
toolCall.indeterminateReason = input.reason;
|
|
5571
|
+
toolCall.updatedAt = timestamp;
|
|
5572
|
+
this.recordEvent("tool_call_indeterminate", {
|
|
5573
|
+
agentId: toolCall.agentId,
|
|
5574
|
+
runId: toolCall.runId,
|
|
5575
|
+
toolCallId: toolCall.id
|
|
5576
|
+
}, { reason: input.reason });
|
|
5577
|
+
return clone2(toolCall);
|
|
5578
|
+
}
|
|
5579
|
+
async getToolCall(toolCallId) {
|
|
5580
|
+
const toolCall = this.toolCalls.get(toolCallId);
|
|
5581
|
+
return toolCall ? clone2(toolCall) : void 0;
|
|
5582
|
+
}
|
|
5583
|
+
async listEvents(cursor = {}) {
|
|
5584
|
+
let events = this.events;
|
|
5585
|
+
if (cursor.after) {
|
|
5586
|
+
const index = events.findIndex((event) => event.id === cursor.after);
|
|
5587
|
+
if (index >= 0) events = events.slice(index + 1);
|
|
5588
|
+
}
|
|
5589
|
+
return clone2(cursor.limit === void 0 ? events : events.slice(0, cursor.limit));
|
|
5590
|
+
}
|
|
5591
|
+
async recordEvent(input, related = {}, payload) {
|
|
5592
|
+
this.recordEventInternal(typeof input === "string" ? input : input.kind, typeof input === "string" ? related : input.related ?? {}, typeof input === "string" ? payload : input.payload);
|
|
5593
|
+
return clone2(this.events.at(-1));
|
|
5594
|
+
}
|
|
5595
|
+
async getEventCheckpoint(consumerId) {
|
|
5596
|
+
return clone2(this.eventCheckpoints.get(consumerId) ?? {
|
|
5597
|
+
consumerId,
|
|
5598
|
+
sequence: 0,
|
|
5599
|
+
updatedAt: createTimestamp(/* @__PURE__ */ new Date(0))
|
|
5600
|
+
});
|
|
5601
|
+
}
|
|
5602
|
+
async acknowledgeEvent(consumerId, eventId) {
|
|
5603
|
+
const event = this.events.find((candidate) => candidate.id === eventId);
|
|
5604
|
+
if (!event) throw new Error(`Runtime Event not found: ${eventId}.`);
|
|
5605
|
+
const current = await this.getEventCheckpoint(consumerId);
|
|
5606
|
+
if (event.sequence <= current.sequence) return current;
|
|
5607
|
+
if (event.sequence !== current.sequence + 1) {
|
|
5608
|
+
throw new Error(`Runtime Event Consumer Checkpoint cannot advance from sequence ${current.sequence} to ${event.sequence}.`);
|
|
5609
|
+
}
|
|
5610
|
+
const checkpoint = {
|
|
5611
|
+
consumerId,
|
|
5612
|
+
sequence: event.sequence,
|
|
5613
|
+
eventId: event.id,
|
|
5614
|
+
updatedAt: createTimestamp()
|
|
5615
|
+
};
|
|
5616
|
+
this.eventCheckpoints.set(consumerId, checkpoint);
|
|
5617
|
+
return clone2(checkpoint);
|
|
5618
|
+
}
|
|
5619
|
+
async acknowledgeEventAndEnqueueAgentInput(input) {
|
|
5620
|
+
const event = this.events.find((candidate) => candidate.id === input.eventId);
|
|
5621
|
+
if (!event) throw new Error(`Runtime Event not found: ${input.eventId}.`);
|
|
5622
|
+
const current = this.eventCheckpoints.get(input.consumerId) ?? {
|
|
5623
|
+
consumerId: input.consumerId,
|
|
5624
|
+
sequence: 0,
|
|
5625
|
+
updatedAt: createTimestamp(/* @__PURE__ */ new Date(0))
|
|
5626
|
+
};
|
|
5627
|
+
if (event.sequence <= current.sequence) return { checkpoint: clone2(current) };
|
|
5628
|
+
if (event.sequence !== current.sequence + 1) {
|
|
5629
|
+
throw new Error(`Runtime Event Consumer Checkpoint cannot advance from sequence ${current.sequence} to ${event.sequence}.`);
|
|
5630
|
+
}
|
|
5631
|
+
this.requireAgent(input.agentId);
|
|
5632
|
+
const enqueued = this.enqueueAgentInputState(input);
|
|
5633
|
+
const checkpoint = {
|
|
5634
|
+
consumerId: input.consumerId,
|
|
5635
|
+
sequence: event.sequence,
|
|
5636
|
+
eventId: event.id,
|
|
5637
|
+
updatedAt: createTimestamp()
|
|
5638
|
+
};
|
|
5639
|
+
this.eventCheckpoints.set(input.consumerId, checkpoint);
|
|
5640
|
+
return { checkpoint: clone2(checkpoint), enqueued };
|
|
5641
|
+
}
|
|
5642
|
+
requireAgent(agentId) {
|
|
5643
|
+
const agent = this.agents.get(agentId);
|
|
5644
|
+
if (!agent) {
|
|
5645
|
+
throw new Error(`Agent not found: ${agentId}.`);
|
|
5646
|
+
}
|
|
5647
|
+
return agent;
|
|
5648
|
+
}
|
|
5649
|
+
requireMessage(messageId) {
|
|
5650
|
+
const message = this.messages.get(messageId);
|
|
5651
|
+
if (!message) {
|
|
5652
|
+
throw new Error(`Runtime Message not found: ${messageId}.`);
|
|
5653
|
+
}
|
|
5654
|
+
return message;
|
|
5655
|
+
}
|
|
5656
|
+
requireRun(runId) {
|
|
5657
|
+
const run = this.runs.get(runId);
|
|
5658
|
+
if (!run) {
|
|
5659
|
+
throw new Error(`Agent Run not found: ${runId}.`);
|
|
5660
|
+
}
|
|
5661
|
+
return run;
|
|
5662
|
+
}
|
|
5663
|
+
requireToolCall(toolCallId) {
|
|
5664
|
+
const toolCall = this.toolCalls.get(toolCallId);
|
|
5665
|
+
if (!toolCall) {
|
|
5666
|
+
throw new Error(`Tool Call not found: ${toolCallId}.`);
|
|
5667
|
+
}
|
|
5668
|
+
return toolCall;
|
|
5669
|
+
}
|
|
5670
|
+
recordEventInternal(kind, related, payload) {
|
|
5671
|
+
this.events.push({
|
|
5672
|
+
id: createId("evt"),
|
|
5673
|
+
sequence: this.events.length + 1,
|
|
5674
|
+
kind,
|
|
5675
|
+
...related,
|
|
5676
|
+
...payload !== void 0 ? { payload: clone2(payload) } : {},
|
|
5677
|
+
createdAt: createTimestamp()
|
|
5678
|
+
});
|
|
5679
|
+
}
|
|
5680
|
+
};
|
|
5681
|
+
|
|
5682
|
+
// src/runtime/sqlite-store.ts
|
|
5683
|
+
import { Database } from "bun:sqlite";
|
|
5684
|
+
|
|
5685
|
+
// src/runtime/runtime-schema.ts
|
|
5686
|
+
var RUNTIME_SCHEMA_SQL = `
|
|
5687
|
+
CREATE TABLE IF NOT EXISTS agents (
|
|
5688
|
+
id TEXT PRIMARY KEY NOT NULL,
|
|
5689
|
+
session_id TEXT NOT NULL,
|
|
5690
|
+
state TEXT NOT NULL CHECK (state IN ('active', 'paused')),
|
|
5691
|
+
created_at TEXT NOT NULL,
|
|
5692
|
+
updated_at TEXT NOT NULL
|
|
5693
|
+
);
|
|
5694
|
+
|
|
5695
|
+
CREATE TABLE IF NOT EXISTS runtime_messages (
|
|
5696
|
+
id TEXT PRIMARY KEY NOT NULL,
|
|
5697
|
+
agent_id TEXT NOT NULL REFERENCES agents(id),
|
|
5698
|
+
kind TEXT NOT NULL CHECK (kind IN ('agent_input')),
|
|
5699
|
+
payload_json TEXT NOT NULL,
|
|
5700
|
+
state TEXT NOT NULL CHECK (state IN ('queued', 'leased', 'acknowledged', 'dead_lettered')),
|
|
5701
|
+
attempts INTEGER NOT NULL DEFAULT 0,
|
|
5702
|
+
run_id TEXT,
|
|
5703
|
+
dead_letter_reason TEXT,
|
|
5704
|
+
created_at TEXT NOT NULL,
|
|
5705
|
+
updated_at TEXT NOT NULL
|
|
5706
|
+
);
|
|
5707
|
+
|
|
5708
|
+
CREATE TABLE IF NOT EXISTS agent_runs (
|
|
5709
|
+
id TEXT PRIMARY KEY NOT NULL,
|
|
5710
|
+
agent_id TEXT NOT NULL REFERENCES agents(id),
|
|
5711
|
+
message_id TEXT NOT NULL REFERENCES runtime_messages(id),
|
|
5712
|
+
state TEXT NOT NULL CHECK (state IN ('queued', 'running', 'suspended', 'completed', 'failed', 'cancelled')),
|
|
5713
|
+
attempt INTEGER NOT NULL DEFAULT 0,
|
|
5714
|
+
lease_id TEXT,
|
|
5715
|
+
outcome_json TEXT,
|
|
5716
|
+
suspension_reason TEXT,
|
|
5717
|
+
input_request_json TEXT,
|
|
5718
|
+
execution_state_json TEXT,
|
|
5719
|
+
metadata_json TEXT,
|
|
5720
|
+
created_at TEXT NOT NULL,
|
|
5721
|
+
updated_at TEXT NOT NULL
|
|
5722
|
+
);
|
|
5723
|
+
|
|
5724
|
+
CREATE TABLE IF NOT EXISTS runtime_leases (
|
|
5725
|
+
id TEXT PRIMARY KEY NOT NULL,
|
|
5726
|
+
run_id TEXT NOT NULL UNIQUE REFERENCES agent_runs(id),
|
|
5727
|
+
worker_id TEXT NOT NULL,
|
|
5728
|
+
leased_at TEXT NOT NULL,
|
|
5729
|
+
expires_at TEXT NOT NULL
|
|
5730
|
+
);
|
|
5731
|
+
|
|
5732
|
+
CREATE TABLE IF NOT EXISTS runtime_events (
|
|
5733
|
+
id TEXT PRIMARY KEY NOT NULL,
|
|
5734
|
+
sequence INTEGER NOT NULL DEFAULT 0,
|
|
5735
|
+
kind TEXT NOT NULL,
|
|
5736
|
+
agent_id TEXT,
|
|
5737
|
+
message_id TEXT,
|
|
5738
|
+
run_id TEXT,
|
|
5739
|
+
tool_call_id TEXT,
|
|
5740
|
+
payload_json TEXT,
|
|
5741
|
+
created_at TEXT NOT NULL
|
|
5742
|
+
);
|
|
5743
|
+
|
|
5744
|
+
CREATE TABLE IF NOT EXISTS runtime_tool_calls (
|
|
5745
|
+
id TEXT PRIMARY KEY NOT NULL,
|
|
5746
|
+
agent_id TEXT NOT NULL REFERENCES agents(id),
|
|
5747
|
+
run_id TEXT NOT NULL REFERENCES agent_runs(id),
|
|
5748
|
+
name TEXT NOT NULL,
|
|
5749
|
+
args_json TEXT NOT NULL,
|
|
5750
|
+
state TEXT NOT NULL CHECK (state IN ('queued', 'running', 'completed', 'failed', 'indeterminate')),
|
|
5751
|
+
result_json TEXT,
|
|
5752
|
+
indeterminate_reason TEXT,
|
|
5753
|
+
created_at TEXT NOT NULL,
|
|
5754
|
+
updated_at TEXT NOT NULL
|
|
5755
|
+
);
|
|
5756
|
+
|
|
5757
|
+
CREATE TABLE IF NOT EXISTS runtime_event_consumers (
|
|
5758
|
+
consumer_id TEXT PRIMARY KEY NOT NULL,
|
|
5759
|
+
sequence INTEGER NOT NULL DEFAULT 0,
|
|
5760
|
+
event_id TEXT,
|
|
5761
|
+
updated_at TEXT NOT NULL
|
|
5762
|
+
);
|
|
5763
|
+
|
|
5764
|
+
CREATE INDEX IF NOT EXISTS runtime_messages_runnable_idx
|
|
5765
|
+
ON runtime_messages (agent_id, state, created_at);
|
|
5766
|
+
CREATE INDEX IF NOT EXISTS agent_runs_agent_state_idx
|
|
5767
|
+
ON agent_runs (agent_id, state, created_at);
|
|
5768
|
+
CREATE INDEX IF NOT EXISTS runtime_events_delivery_idx
|
|
5769
|
+
ON runtime_events (sequence);
|
|
5770
|
+
|
|
5771
|
+
CREATE INDEX IF NOT EXISTS agent_runs_runnable_idx
|
|
5772
|
+
ON agent_runs (state, created_at);
|
|
5773
|
+
`;
|
|
5774
|
+
function initializeRuntimeSchema(database) {
|
|
5775
|
+
const initialize = database.transaction(() => {
|
|
5776
|
+
for (const statement of RUNTIME_SCHEMA_SQL.split(";")) {
|
|
5777
|
+
const sql = statement.trim();
|
|
5778
|
+
if (sql.length > 0) {
|
|
5779
|
+
database.run(sql);
|
|
5780
|
+
}
|
|
5781
|
+
}
|
|
5782
|
+
try {
|
|
5783
|
+
database.run("ALTER TABLE agent_runs ADD COLUMN metadata_json TEXT");
|
|
5784
|
+
} catch (error) {
|
|
5785
|
+
if (!(error instanceof Error) || !/duplicate column name/i.test(error.message)) throw error;
|
|
5786
|
+
}
|
|
5787
|
+
});
|
|
5788
|
+
initialize();
|
|
5789
|
+
}
|
|
5790
|
+
|
|
5791
|
+
// src/runtime/sqlite-store.ts
|
|
5792
|
+
function clone3(value) {
|
|
5793
|
+
return structuredClone(value);
|
|
5794
|
+
}
|
|
5795
|
+
function json(value) {
|
|
5796
|
+
return JSON.stringify(value);
|
|
5797
|
+
}
|
|
5798
|
+
function parse(value) {
|
|
5799
|
+
return JSON.parse(value);
|
|
5800
|
+
}
|
|
5801
|
+
function optionalJson(value) {
|
|
5802
|
+
return value === null ? void 0 : parse(value);
|
|
5803
|
+
}
|
|
5804
|
+
function transitionError2(entity, id, state, action) {
|
|
5805
|
+
return new Error(`Cannot ${action} ${entity} ${id} from state "${state}".`);
|
|
5806
|
+
}
|
|
5807
|
+
function requireTransition2(entity, id, state, action, allowedStates) {
|
|
5808
|
+
if (!allowedStates.includes(state)) {
|
|
5809
|
+
throw transitionError2(entity, id, state, action);
|
|
5810
|
+
}
|
|
5811
|
+
}
|
|
5812
|
+
function timestampFor(input) {
|
|
5813
|
+
const date = input ? new Date(input) : /* @__PURE__ */ new Date();
|
|
5814
|
+
return { date, timestamp: createTimestamp(date) };
|
|
5815
|
+
}
|
|
5816
|
+
var SqliteRuntimeStateStore = class {
|
|
5817
|
+
database;
|
|
5818
|
+
constructor(filename = ":memory:") {
|
|
5819
|
+
this.database = new Database(filename, { create: true, readwrite: true, strict: true });
|
|
5820
|
+
this.database.run("PRAGMA foreign_keys = ON");
|
|
5821
|
+
initializeRuntimeSchema(this.database);
|
|
5822
|
+
}
|
|
5823
|
+
close() {
|
|
5824
|
+
this.database.close();
|
|
5825
|
+
}
|
|
5826
|
+
async createAgent(input) {
|
|
5827
|
+
const { timestamp } = timestampFor();
|
|
5828
|
+
const agent = {
|
|
5829
|
+
id: createId("agt"),
|
|
5830
|
+
sessionId: input.sessionId,
|
|
5831
|
+
state: "active",
|
|
5832
|
+
createdAt: timestamp,
|
|
5833
|
+
updatedAt: timestamp
|
|
5834
|
+
};
|
|
5835
|
+
const create = this.database.transaction(() => {
|
|
5836
|
+
this.database.run(
|
|
5837
|
+
"INSERT INTO agents (id, session_id, state, created_at, updated_at) VALUES (?, ?, ?, ?, ?)",
|
|
5838
|
+
[agent.id, agent.sessionId, agent.state, agent.createdAt, agent.updatedAt]
|
|
5839
|
+
);
|
|
5840
|
+
this.recordEvent("agent_created", { agentId: agent.id });
|
|
5841
|
+
});
|
|
5842
|
+
create();
|
|
5843
|
+
return clone3(agent);
|
|
5844
|
+
}
|
|
5845
|
+
async getAgent(agentId) {
|
|
5846
|
+
const row = this.database.query("SELECT * FROM agents WHERE id = ?").get(agentId);
|
|
5847
|
+
return row ? this.agentFromRow(row) : void 0;
|
|
5848
|
+
}
|
|
5849
|
+
async listAgents() {
|
|
5850
|
+
const rows = this.database.query("SELECT * FROM agents ORDER BY created_at, id").all();
|
|
5851
|
+
return rows.map((row) => this.agentFromRow(row));
|
|
5852
|
+
}
|
|
5853
|
+
async setAgentState(agentId, state) {
|
|
5854
|
+
const current = this.requireAgent(agentId);
|
|
5855
|
+
if (current.state === state) return clone3(current);
|
|
5856
|
+
const { timestamp } = timestampFor();
|
|
5857
|
+
const updated = this.database.transaction(() => {
|
|
5858
|
+
this.database.run("UPDATE agents SET state = ?, updated_at = ? WHERE id = ?", [state, timestamp, agentId]);
|
|
5859
|
+
this.recordEvent(state === "paused" ? "agent_paused" : "agent_resumed", { agentId });
|
|
5860
|
+
return this.requireAgent(agentId);
|
|
5861
|
+
})();
|
|
5862
|
+
return clone3(updated);
|
|
5863
|
+
}
|
|
5864
|
+
async enqueueAgentInput(input) {
|
|
5865
|
+
const enqueue = this.database.transaction(() => this.enqueueAgentInputState(input));
|
|
5866
|
+
return clone3(enqueue());
|
|
5867
|
+
}
|
|
5868
|
+
enqueueAgentInputState(input) {
|
|
5869
|
+
this.requireAgent(input.agentId);
|
|
5870
|
+
const { timestamp } = timestampFor();
|
|
5871
|
+
const messageId = createId("rmsg");
|
|
5872
|
+
const suspendedRow = this.database.query(
|
|
5873
|
+
"SELECT * FROM agent_runs WHERE agent_id = ? AND state = 'suspended' ORDER BY created_at, id LIMIT 1"
|
|
5874
|
+
).get(input.agentId);
|
|
5875
|
+
const runId = suspendedRow?.id ?? createId("run");
|
|
5876
|
+
const message = {
|
|
5877
|
+
id: messageId,
|
|
5878
|
+
agentId: input.agentId,
|
|
5879
|
+
kind: "agent_input",
|
|
5880
|
+
input: clone3(input.input),
|
|
5881
|
+
state: "queued",
|
|
5882
|
+
attempts: 0,
|
|
5883
|
+
runId,
|
|
5884
|
+
createdAt: timestamp,
|
|
5885
|
+
updatedAt: timestamp
|
|
5886
|
+
};
|
|
5887
|
+
const run = {
|
|
5888
|
+
id: runId,
|
|
5889
|
+
agentId: input.agentId,
|
|
5890
|
+
messageId,
|
|
5891
|
+
state: "queued",
|
|
5892
|
+
attempt: 0,
|
|
5893
|
+
...suspendedRow?.metadata_json ? { metadata: parse(suspendedRow.metadata_json) } : input.input.metadata ? { metadata: clone3(input.input.metadata) } : {},
|
|
5894
|
+
createdAt: timestamp,
|
|
5895
|
+
updatedAt: timestamp
|
|
5896
|
+
};
|
|
5897
|
+
if (suspendedRow) {
|
|
5898
|
+
message.runId = runId;
|
|
5899
|
+
}
|
|
5900
|
+
this.insertMessage(message);
|
|
5901
|
+
if (suspendedRow) {
|
|
5902
|
+
this.database.run(
|
|
5903
|
+
"UPDATE agent_runs SET message_id = ?, state = 'queued', lease_id = NULL, suspension_reason = NULL, input_request_json = NULL, updated_at = ? WHERE id = ?",
|
|
5904
|
+
[messageId, timestamp, runId]
|
|
5905
|
+
);
|
|
5906
|
+
} else {
|
|
5907
|
+
this.database.run(
|
|
5908
|
+
"INSERT INTO agent_runs (id, agent_id, message_id, state, attempt, lease_id, outcome_json, suspension_reason, input_request_json, execution_state_json, metadata_json, created_at, updated_at) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)",
|
|
5909
|
+
[run.id, run.agentId, run.messageId, run.state, run.attempt, null, null, null, null, null, run.metadata ? json(run.metadata) : null, run.createdAt, run.updatedAt]
|
|
5910
|
+
);
|
|
5911
|
+
}
|
|
5912
|
+
this.recordEventInternal("message_enqueued", { agentId: input.agentId, messageId });
|
|
5913
|
+
const storedRun = this.requireRun(runId);
|
|
5914
|
+
this.recordEventInternal("run_enqueued", { agentId: input.agentId, messageId, runId }, runEventPayload(storedRun.metadata));
|
|
5915
|
+
return { message: clone3(message), run: clone3(storedRun), resumed: Boolean(suspendedRow) };
|
|
5916
|
+
}
|
|
5917
|
+
async getMessage(messageId) {
|
|
5918
|
+
const row = this.messageRow(messageId);
|
|
5919
|
+
return row ? this.messageFromRow(row) : void 0;
|
|
5920
|
+
}
|
|
5921
|
+
async getRun(runId) {
|
|
5922
|
+
const row = this.runRow(runId);
|
|
5923
|
+
return row ? this.runFromRow(row) : void 0;
|
|
5924
|
+
}
|
|
5925
|
+
async listRuns(input = {}) {
|
|
5926
|
+
const clauses = [];
|
|
5927
|
+
const params = [];
|
|
5928
|
+
if (input.agentId) {
|
|
5929
|
+
clauses.push("agent_id = ?");
|
|
5930
|
+
params.push(input.agentId);
|
|
5931
|
+
}
|
|
5932
|
+
if (input.states && input.states.length > 0) {
|
|
5933
|
+
clauses.push(`state IN (${input.states.map(() => "?").join(", ")})`);
|
|
5934
|
+
params.push(...input.states);
|
|
5935
|
+
}
|
|
5936
|
+
const where = clauses.length > 0 ? `WHERE ${clauses.join(" AND ")}` : "";
|
|
5937
|
+
const rows = this.database.query(`SELECT * FROM agent_runs ${where} ORDER BY created_at, id`).all(...params);
|
|
5938
|
+
return rows.map((row) => this.runFromRow(row));
|
|
5939
|
+
}
|
|
5940
|
+
async listActiveRuns() {
|
|
5941
|
+
const rows = this.database.query(
|
|
5942
|
+
"SELECT * FROM agent_runs WHERE state IN ('queued', 'running', 'suspended') ORDER BY created_at, id"
|
|
5943
|
+
).all();
|
|
5944
|
+
return rows.map((row) => this.runFromRow(row));
|
|
5945
|
+
}
|
|
5946
|
+
async leaseRun(input) {
|
|
5947
|
+
const lease = this.database.transaction(() => {
|
|
5948
|
+
const run2 = this.requireRun(input.runId);
|
|
5949
|
+
requireTransition2("Agent Run", run2.id, run2.state, "lease", ["queued"]);
|
|
5950
|
+
const message2 = this.requireMessage(run2.messageId);
|
|
5951
|
+
requireTransition2("Runtime Message", message2.id, message2.state, "lease", ["queued"]);
|
|
5952
|
+
const agent = this.requireAgent(run2.agentId);
|
|
5953
|
+
if (agent.state !== "active") {
|
|
5954
|
+
throw transitionError2("Agent", agent.id, agent.state, "lease a Run for");
|
|
5955
|
+
}
|
|
5956
|
+
const activeRun = this.database.query(
|
|
5957
|
+
"SELECT id FROM agent_runs WHERE agent_id = ? AND id <> ? AND state IN ('running', 'suspended') LIMIT 1"
|
|
5958
|
+
).get(run2.agentId, run2.id);
|
|
5959
|
+
if (activeRun) {
|
|
5960
|
+
throw new Error(`Agent ${run2.agentId} already has an active Run.`);
|
|
5961
|
+
}
|
|
5962
|
+
if (!Number.isFinite(input.leaseDurationMs) || input.leaseDurationMs <= 0) {
|
|
5963
|
+
throw new Error("Lease duration must be a positive finite number.");
|
|
5964
|
+
}
|
|
5965
|
+
const { date, timestamp } = timestampFor(input.now);
|
|
5966
|
+
const runtimeLease = {
|
|
5967
|
+
id: createId("lease"),
|
|
5968
|
+
runId: run2.id,
|
|
5969
|
+
workerId: input.workerId,
|
|
5970
|
+
leasedAt: timestamp,
|
|
5971
|
+
expiresAt: createTimestamp(new Date(date.getTime() + input.leaseDurationMs))
|
|
5972
|
+
};
|
|
5973
|
+
this.database.run(
|
|
5974
|
+
"INSERT INTO runtime_leases (id, run_id, worker_id, leased_at, expires_at) VALUES (?, ?, ?, ?, ?)",
|
|
5975
|
+
[runtimeLease.id, runtimeLease.runId, runtimeLease.workerId, runtimeLease.leasedAt, runtimeLease.expiresAt]
|
|
5976
|
+
);
|
|
5977
|
+
this.database.run(
|
|
5978
|
+
"UPDATE agent_runs SET state = ?, attempt = attempt + 1, lease_id = ?, updated_at = ? WHERE id = ?",
|
|
5979
|
+
["running", runtimeLease.id, timestamp, run2.id]
|
|
5980
|
+
);
|
|
5981
|
+
this.database.run(
|
|
5982
|
+
"UPDATE runtime_messages SET state = ?, attempts = attempts + 1, updated_at = ? WHERE id = ?",
|
|
5983
|
+
["leased", timestamp, message2.id]
|
|
5984
|
+
);
|
|
5985
|
+
this.recordEvent("run_leased", { agentId: run2.agentId, messageId: message2.id, runId: run2.id }, runtimeLease);
|
|
5986
|
+
return runtimeLease;
|
|
5987
|
+
})();
|
|
5988
|
+
const run = this.requireRun(input.runId);
|
|
5989
|
+
const message = this.requireMessage(run.messageId);
|
|
5990
|
+
return { run, message, lease: clone3(lease) };
|
|
5991
|
+
}
|
|
5992
|
+
async renewLease(input) {
|
|
5993
|
+
const lease = this.database.transaction(() => {
|
|
5994
|
+
const run = this.requireRun(input.runId);
|
|
5995
|
+
requireTransition2("Agent Run", run.id, run.state, "renew Lease", ["running"]);
|
|
5996
|
+
const message = this.requireMessage(run.messageId);
|
|
5997
|
+
requireTransition2("Runtime Message", message.id, message.state, "renew Lease", ["leased"]);
|
|
5998
|
+
if (run.leaseId !== input.leaseId || message.lease?.id !== input.leaseId) {
|
|
5999
|
+
throw new Error(`Lease ${input.leaseId} does not own Agent Run ${run.id}.`);
|
|
6000
|
+
}
|
|
6001
|
+
if (!Number.isFinite(input.leaseDurationMs) || input.leaseDurationMs <= 0) {
|
|
6002
|
+
throw new Error("Lease duration must be a positive finite number.");
|
|
6003
|
+
}
|
|
6004
|
+
const { date, timestamp } = timestampFor(input.now);
|
|
6005
|
+
const expiresAt = createTimestamp(new Date(date.getTime() + input.leaseDurationMs));
|
|
6006
|
+
this.database.run("UPDATE runtime_leases SET expires_at = ? WHERE id = ?", [expiresAt, input.leaseId]);
|
|
6007
|
+
this.database.run("UPDATE runtime_messages SET updated_at = ? WHERE id = ?", [timestamp, message.id]);
|
|
6008
|
+
return { ...message.lease, expiresAt };
|
|
6009
|
+
})();
|
|
6010
|
+
return clone3(lease);
|
|
6011
|
+
}
|
|
6012
|
+
async suspendRun(input) {
|
|
6013
|
+
const run = this.database.transaction(() => {
|
|
6014
|
+
const current = this.requireRun(input.runId);
|
|
6015
|
+
requireTransition2("Agent Run", current.id, current.state, "suspend", ["running"]);
|
|
6016
|
+
const message = this.requireMessage(current.messageId);
|
|
6017
|
+
requireTransition2("Runtime Message", message.id, message.state, "acknowledge", ["leased"]);
|
|
6018
|
+
const { timestamp } = timestampFor();
|
|
6019
|
+
this.database.run(
|
|
6020
|
+
"UPDATE agent_runs SET state = ?, lease_id = NULL, suspension_reason = ?, input_request_json = ?, execution_state_json = ?, updated_at = ? WHERE id = ?",
|
|
6021
|
+
["suspended", input.reason ?? null, input.inputRequest ? json(input.inputRequest) : null, input.executionState ? json(input.executionState) : null, timestamp, current.id]
|
|
6022
|
+
);
|
|
6023
|
+
this.database.run(
|
|
6024
|
+
"UPDATE runtime_messages SET state = ?, updated_at = ? WHERE id = ?",
|
|
6025
|
+
["acknowledged", timestamp, message.id]
|
|
6026
|
+
);
|
|
6027
|
+
this.database.run("DELETE FROM runtime_leases WHERE run_id = ?", [current.id]);
|
|
6028
|
+
this.recordEvent("run_suspended", { agentId: current.agentId, messageId: message.id, runId: current.id }, runEventPayload(current.metadata, {
|
|
6029
|
+
reason: input.reason
|
|
6030
|
+
}));
|
|
6031
|
+
return this.requireRun(current.id);
|
|
6032
|
+
})();
|
|
6033
|
+
return clone3(run);
|
|
6034
|
+
}
|
|
6035
|
+
async completeRun(input) {
|
|
6036
|
+
const run = this.database.transaction(() => {
|
|
6037
|
+
const current = this.requireRun(input.runId);
|
|
6038
|
+
requireTransition2("Agent Run", current.id, current.state, "complete", ["running"]);
|
|
6039
|
+
const message = this.requireMessage(current.messageId);
|
|
6040
|
+
requireTransition2("Runtime Message", message.id, message.state, "acknowledge", ["leased"]);
|
|
6041
|
+
const { timestamp } = timestampFor();
|
|
6042
|
+
const state = input.state ?? "completed";
|
|
6043
|
+
this.database.run(
|
|
6044
|
+
"UPDATE agent_runs SET state = ?, lease_id = NULL, outcome_json = ?, input_request_json = NULL, execution_state_json = NULL, updated_at = ? WHERE id = ?",
|
|
6045
|
+
[state, json(input.outcome), timestamp, current.id]
|
|
6046
|
+
);
|
|
6047
|
+
this.database.run(
|
|
6048
|
+
"UPDATE runtime_messages SET state = 'acknowledged', updated_at = ? WHERE id = ?",
|
|
6049
|
+
[timestamp, message.id]
|
|
6050
|
+
);
|
|
6051
|
+
this.database.run("DELETE FROM runtime_leases WHERE run_id = ?", [current.id]);
|
|
6052
|
+
this.recordEvent("run_completed", { agentId: current.agentId, messageId: current.messageId, runId: current.id }, runEventPayload(current.metadata, {
|
|
6053
|
+
state,
|
|
6054
|
+
outcome: input.outcome
|
|
6055
|
+
}));
|
|
6056
|
+
this.recordEvent("message_acknowledged", { agentId: message.agentId, messageId: message.id });
|
|
6057
|
+
return this.requireRun(current.id);
|
|
6058
|
+
})();
|
|
6059
|
+
return clone3(run);
|
|
6060
|
+
}
|
|
6061
|
+
async retryRun(input) {
|
|
6062
|
+
const run = this.database.transaction(() => {
|
|
6063
|
+
const current = this.requireRun(input.runId);
|
|
6064
|
+
requireTransition2("Agent Run", current.id, current.state, "retry", ["running"]);
|
|
6065
|
+
const message = this.requireMessage(current.messageId);
|
|
6066
|
+
requireTransition2("Runtime Message", message.id, message.state, "retry", ["leased"]);
|
|
6067
|
+
const { timestamp } = timestampFor();
|
|
6068
|
+
this.database.run(
|
|
6069
|
+
"UPDATE agent_runs SET state = 'queued', lease_id = NULL, updated_at = ? WHERE id = ?",
|
|
6070
|
+
[timestamp, current.id]
|
|
6071
|
+
);
|
|
6072
|
+
this.database.run(
|
|
6073
|
+
"UPDATE runtime_messages SET state = 'queued', updated_at = ? WHERE id = ?",
|
|
6074
|
+
[timestamp, message.id]
|
|
6075
|
+
);
|
|
6076
|
+
this.database.run("DELETE FROM runtime_leases WHERE run_id = ?", [current.id]);
|
|
6077
|
+
this.recordEvent("run_retry_scheduled", {
|
|
6078
|
+
agentId: current.agentId,
|
|
6079
|
+
messageId: message.id,
|
|
6080
|
+
runId: current.id
|
|
6081
|
+
}, { reason: input.reason, attempt: current.attempt });
|
|
6082
|
+
return this.requireRun(current.id);
|
|
6083
|
+
})();
|
|
6084
|
+
return clone3(run);
|
|
6085
|
+
}
|
|
6086
|
+
async exhaustRun(input) {
|
|
6087
|
+
const run = this.database.transaction(() => {
|
|
6088
|
+
const current = this.requireRun(input.runId);
|
|
6089
|
+
requireTransition2("Agent Run", current.id, current.state, "exhaust retries", ["running"]);
|
|
6090
|
+
const message = this.requireMessage(current.messageId);
|
|
6091
|
+
requireTransition2("Runtime Message", message.id, message.state, "dead-letter", ["leased"]);
|
|
6092
|
+
const { timestamp } = timestampFor();
|
|
6093
|
+
this.database.run(
|
|
6094
|
+
"UPDATE agent_runs SET state = 'failed', lease_id = NULL, outcome_json = ?, input_request_json = NULL, execution_state_json = NULL, updated_at = ? WHERE id = ?",
|
|
6095
|
+
[json(input.outcome), timestamp, current.id]
|
|
6096
|
+
);
|
|
6097
|
+
this.database.run(
|
|
6098
|
+
"UPDATE runtime_messages SET state = 'dead_lettered', dead_letter_reason = ?, updated_at = ? WHERE id = ?",
|
|
6099
|
+
[input.reason, timestamp, message.id]
|
|
6100
|
+
);
|
|
6101
|
+
this.database.run("DELETE FROM runtime_leases WHERE run_id = ?", [current.id]);
|
|
6102
|
+
this.recordEvent("run_completed", { agentId: current.agentId, messageId: message.id, runId: current.id }, runEventPayload(current.metadata, {
|
|
6103
|
+
state: "failed",
|
|
6104
|
+
outcome: input.outcome
|
|
6105
|
+
}));
|
|
6106
|
+
this.recordEvent("message_dead_lettered", { agentId: current.agentId, messageId: message.id }, {
|
|
6107
|
+
reason: input.reason
|
|
6108
|
+
});
|
|
6109
|
+
return this.requireRun(current.id);
|
|
6110
|
+
})();
|
|
6111
|
+
return clone3(run);
|
|
6112
|
+
}
|
|
6113
|
+
async abortRun(input) {
|
|
6114
|
+
const run = this.database.transaction(() => {
|
|
6115
|
+
const current = this.requireRun(input.runId);
|
|
6116
|
+
requireTransition2("Agent Run", current.id, current.state, "abort", ["queued", "running", "suspended"]);
|
|
6117
|
+
const message = this.requireMessage(current.messageId);
|
|
6118
|
+
const { timestamp } = timestampFor();
|
|
6119
|
+
this.database.run(
|
|
6120
|
+
"UPDATE agent_runs SET state = 'cancelled', lease_id = NULL, outcome_json = ?, input_request_json = NULL, execution_state_json = NULL, updated_at = ? WHERE id = ?",
|
|
6121
|
+
[json(input.outcome), timestamp, current.id]
|
|
6122
|
+
);
|
|
6123
|
+
if (message.state === "queued" || message.state === "leased") {
|
|
6124
|
+
this.database.run("UPDATE runtime_messages SET state = 'acknowledged', updated_at = ? WHERE id = ?", [
|
|
6125
|
+
timestamp,
|
|
6126
|
+
message.id
|
|
6127
|
+
]);
|
|
6128
|
+
}
|
|
6129
|
+
this.database.run("DELETE FROM runtime_leases WHERE run_id = ?", [current.id]);
|
|
6130
|
+
this.recordEvent("run_aborted", { agentId: current.agentId, messageId: message.id, runId: current.id }, runEventPayload(current.metadata, {
|
|
6131
|
+
state: "cancelled",
|
|
6132
|
+
outcome: input.outcome
|
|
6133
|
+
}));
|
|
6134
|
+
return this.requireRun(current.id);
|
|
6135
|
+
})();
|
|
6136
|
+
return clone3(run);
|
|
6137
|
+
}
|
|
6138
|
+
async recoverExpiredLeases(input) {
|
|
6139
|
+
const { timestamp } = timestampFor(input);
|
|
6140
|
+
const rows = this.database.query(`
|
|
6141
|
+
SELECT r.id
|
|
6142
|
+
FROM agent_runs r
|
|
6143
|
+
JOIN runtime_leases l ON l.id = r.lease_id
|
|
6144
|
+
WHERE r.state = 'running' AND l.expires_at <= ?
|
|
6145
|
+
ORDER BY l.expires_at, r.created_at
|
|
6146
|
+
`).all(timestamp);
|
|
6147
|
+
if (rows.length === 0) return [];
|
|
6148
|
+
const recover = this.database.transaction(() => {
|
|
6149
|
+
const recovered = [];
|
|
6150
|
+
for (const row of rows) {
|
|
6151
|
+
const run = this.requireRun(row.id);
|
|
6152
|
+
if (run.state !== "running") continue;
|
|
6153
|
+
const message = this.requireMessage(run.messageId);
|
|
6154
|
+
this.database.run("UPDATE agent_runs SET state = 'queued', lease_id = NULL, updated_at = ? WHERE id = ?", [
|
|
6155
|
+
timestamp,
|
|
6156
|
+
run.id
|
|
6157
|
+
]);
|
|
6158
|
+
this.database.run("UPDATE runtime_messages SET state = 'queued', updated_at = ? WHERE id = ?", [
|
|
6159
|
+
timestamp,
|
|
6160
|
+
message.id
|
|
6161
|
+
]);
|
|
6162
|
+
this.database.run("DELETE FROM runtime_leases WHERE run_id = ?", [run.id]);
|
|
6163
|
+
this.recordEvent("lease_expired", { agentId: run.agentId, messageId: message.id, runId: run.id });
|
|
6164
|
+
recovered.push(this.requireRun(run.id));
|
|
6165
|
+
}
|
|
6166
|
+
return recovered;
|
|
6167
|
+
})();
|
|
6168
|
+
return clone3(recover);
|
|
6169
|
+
}
|
|
6170
|
+
async recoverLeases() {
|
|
6171
|
+
const { timestamp } = timestampFor();
|
|
6172
|
+
const rows = this.database.query(`
|
|
6173
|
+
SELECT r.id
|
|
6174
|
+
FROM agent_runs r
|
|
6175
|
+
JOIN runtime_leases l ON l.id = r.lease_id
|
|
6176
|
+
WHERE r.state = 'running'
|
|
6177
|
+
ORDER BY l.leased_at, r.created_at
|
|
6178
|
+
`).all();
|
|
6179
|
+
if (rows.length === 0) return [];
|
|
6180
|
+
const recovered = this.database.transaction(() => {
|
|
6181
|
+
const runs = [];
|
|
6182
|
+
for (const row of rows) {
|
|
6183
|
+
const run = this.requireRun(row.id);
|
|
6184
|
+
if (run.state !== "running") continue;
|
|
6185
|
+
const message = this.requireMessage(run.messageId);
|
|
6186
|
+
this.database.run("UPDATE agent_runs SET state = 'queued', lease_id = NULL, updated_at = ? WHERE id = ?", [
|
|
6187
|
+
timestamp,
|
|
6188
|
+
run.id
|
|
6189
|
+
]);
|
|
6190
|
+
this.database.run("UPDATE runtime_messages SET state = 'queued', updated_at = ? WHERE id = ?", [
|
|
6191
|
+
timestamp,
|
|
6192
|
+
message.id
|
|
6193
|
+
]);
|
|
6194
|
+
this.database.run("DELETE FROM runtime_leases WHERE run_id = ?", [run.id]);
|
|
6195
|
+
this.recordEvent("lease_recovered", { agentId: run.agentId, messageId: message.id, runId: run.id });
|
|
6196
|
+
runs.push(this.requireRun(run.id));
|
|
6197
|
+
}
|
|
6198
|
+
return runs;
|
|
6199
|
+
})();
|
|
6200
|
+
return clone3(recovered);
|
|
6201
|
+
}
|
|
6202
|
+
async createToolCall(input) {
|
|
6203
|
+
this.requireAgent(input.agentId);
|
|
6204
|
+
this.requireRun(input.runId);
|
|
6205
|
+
const { timestamp } = timestampFor();
|
|
6206
|
+
const toolCall = {
|
|
6207
|
+
id: createId("call"),
|
|
6208
|
+
agentId: input.agentId,
|
|
6209
|
+
runId: input.runId,
|
|
6210
|
+
name: input.name,
|
|
6211
|
+
args: clone3(input.args),
|
|
6212
|
+
state: "queued",
|
|
6213
|
+
createdAt: timestamp,
|
|
6214
|
+
updatedAt: timestamp
|
|
6215
|
+
};
|
|
6216
|
+
const create = this.database.transaction(() => {
|
|
6217
|
+
this.database.run(
|
|
6218
|
+
"INSERT INTO runtime_tool_calls (id, agent_id, run_id, name, args_json, state, result_json, indeterminate_reason, created_at, updated_at) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)",
|
|
6219
|
+
[toolCall.id, toolCall.agentId, toolCall.runId, toolCall.name, json(toolCall.args), toolCall.state, null, null, toolCall.createdAt, toolCall.updatedAt]
|
|
6220
|
+
);
|
|
6221
|
+
this.recordEvent("tool_call_created", { agentId: toolCall.agentId, runId: toolCall.runId, toolCallId: toolCall.id });
|
|
6222
|
+
});
|
|
6223
|
+
create();
|
|
6224
|
+
return clone3(toolCall);
|
|
6225
|
+
}
|
|
6226
|
+
async startToolCall(toolCallId) {
|
|
6227
|
+
const toolCall = this.database.transaction(() => {
|
|
6228
|
+
const current = this.requireToolCall(toolCallId);
|
|
6229
|
+
requireTransition2("Tool Call", current.id, current.state, "start", ["queued"]);
|
|
6230
|
+
const { timestamp } = timestampFor();
|
|
6231
|
+
this.database.run("UPDATE runtime_tool_calls SET state = ?, updated_at = ? WHERE id = ?", [
|
|
6232
|
+
"running",
|
|
6233
|
+
timestamp,
|
|
6234
|
+
current.id
|
|
6235
|
+
]);
|
|
6236
|
+
this.recordEvent("tool_call_started", { agentId: current.agentId, runId: current.runId, toolCallId: current.id });
|
|
6237
|
+
return this.requireToolCall(current.id);
|
|
6238
|
+
})();
|
|
6239
|
+
return clone3(toolCall);
|
|
6240
|
+
}
|
|
6241
|
+
async completeToolCall(input) {
|
|
6242
|
+
const toolCall = this.database.transaction(() => {
|
|
6243
|
+
const current = this.requireToolCall(input.toolCallId);
|
|
6244
|
+
const state = input.state ?? (input.result.ok ? "completed" : "failed");
|
|
6245
|
+
requireTransition2(
|
|
6246
|
+
"Tool Call",
|
|
6247
|
+
current.id,
|
|
6248
|
+
current.state,
|
|
6249
|
+
"complete",
|
|
6250
|
+
state === "failed" ? ["queued", "running"] : ["running"]
|
|
6251
|
+
);
|
|
6252
|
+
const { timestamp } = timestampFor();
|
|
6253
|
+
this.database.run(
|
|
6254
|
+
"UPDATE runtime_tool_calls SET state = ?, result_json = ?, updated_at = ? WHERE id = ?",
|
|
6255
|
+
[state, json(input.result), timestamp, current.id]
|
|
6256
|
+
);
|
|
6257
|
+
this.recordEvent(state === "failed" ? "tool_call_failed" : "tool_call_completed", { agentId: current.agentId, runId: current.runId, toolCallId: current.id }, {
|
|
6258
|
+
state
|
|
6259
|
+
});
|
|
6260
|
+
return this.requireToolCall(current.id);
|
|
6261
|
+
})();
|
|
6262
|
+
return clone3(toolCall);
|
|
6263
|
+
}
|
|
6264
|
+
async markToolCallIndeterminate(input) {
|
|
6265
|
+
const toolCall = this.database.transaction(() => {
|
|
6266
|
+
const current = this.requireToolCall(input.toolCallId);
|
|
6267
|
+
requireTransition2("Tool Call", current.id, current.state, "mark indeterminate", ["running"]);
|
|
6268
|
+
const { timestamp } = timestampFor();
|
|
6269
|
+
this.database.run(
|
|
6270
|
+
"UPDATE runtime_tool_calls SET state = ?, indeterminate_reason = ?, updated_at = ? WHERE id = ?",
|
|
6271
|
+
["indeterminate", input.reason, timestamp, current.id]
|
|
6272
|
+
);
|
|
6273
|
+
this.recordEvent("tool_call_indeterminate", { agentId: current.agentId, runId: current.runId, toolCallId: current.id }, {
|
|
6274
|
+
reason: input.reason
|
|
6275
|
+
});
|
|
6276
|
+
return this.requireToolCall(current.id);
|
|
6277
|
+
})();
|
|
6278
|
+
return clone3(toolCall);
|
|
6279
|
+
}
|
|
6280
|
+
async getToolCall(toolCallId) {
|
|
6281
|
+
const row = this.database.query("SELECT * FROM runtime_tool_calls WHERE id = ?").get(toolCallId);
|
|
6282
|
+
return row ? this.toolCallFromRow(row) : void 0;
|
|
6283
|
+
}
|
|
6284
|
+
async listEvents(cursor = {}) {
|
|
6285
|
+
const after = cursor.after ? this.database.query("SELECT sequence FROM runtime_events WHERE id = ?").get(cursor.after) : null;
|
|
6286
|
+
const limit = cursor.limit === void 0 ? "" : ` LIMIT ${Math.max(0, Math.floor(cursor.limit))}`;
|
|
6287
|
+
const rows = this.database.query(`SELECT * FROM runtime_events ${after ? "WHERE sequence > ?" : ""} ORDER BY sequence${limit}`).all(...after ? [after.sequence] : []);
|
|
6288
|
+
return rows.map((row) => this.eventFromRow(row));
|
|
6289
|
+
}
|
|
6290
|
+
async recordEvent(input, related = {}, payload) {
|
|
6291
|
+
if (typeof input === "string") {
|
|
6292
|
+
this.recordEventInternal(input, related, payload);
|
|
6293
|
+
const row = this.database.query("SELECT * FROM runtime_events ORDER BY sequence DESC LIMIT 1").get();
|
|
6294
|
+
return clone3(this.eventFromRow(row));
|
|
6295
|
+
}
|
|
6296
|
+
const event = this.database.transaction(() => {
|
|
6297
|
+
this.recordEventInternal(input.kind, input.related ?? {}, input.payload);
|
|
6298
|
+
const row = this.database.query("SELECT * FROM runtime_events ORDER BY sequence DESC LIMIT 1").get();
|
|
6299
|
+
return this.eventFromRow(row);
|
|
6300
|
+
})();
|
|
6301
|
+
return clone3(event);
|
|
6302
|
+
}
|
|
6303
|
+
async getEventCheckpoint(consumerId) {
|
|
6304
|
+
return clone3(this.eventCheckpoint(consumerId));
|
|
6305
|
+
}
|
|
6306
|
+
async acknowledgeEvent(consumerId, eventId) {
|
|
6307
|
+
const acknowledge = this.database.transaction(() => this.acknowledgeEventState(consumerId, eventId));
|
|
6308
|
+
const checkpoint = acknowledge();
|
|
6309
|
+
return clone3(checkpoint);
|
|
6310
|
+
}
|
|
6311
|
+
async acknowledgeEventAndEnqueueAgentInput(input) {
|
|
6312
|
+
const deliver = this.database.transaction(() => {
|
|
6313
|
+
const event = this.requireEvent(input.eventId);
|
|
6314
|
+
const current = this.eventCheckpoint(input.consumerId);
|
|
6315
|
+
if (event.sequence <= current.sequence) return { checkpoint: current };
|
|
6316
|
+
this.requireAgent(input.agentId);
|
|
6317
|
+
const enqueued = this.enqueueAgentInputState(input);
|
|
6318
|
+
const checkpoint = this.acknowledgeEventState(input.consumerId, input.eventId);
|
|
6319
|
+
return { checkpoint, enqueued };
|
|
6320
|
+
});
|
|
6321
|
+
return clone3(deliver());
|
|
6322
|
+
}
|
|
6323
|
+
acknowledgeEventState(consumerId, eventId) {
|
|
6324
|
+
const event = this.requireEvent(eventId);
|
|
6325
|
+
const current = this.eventCheckpoint(consumerId);
|
|
6326
|
+
if (event.sequence <= current.sequence) return current;
|
|
6327
|
+
if (event.sequence !== current.sequence + 1) {
|
|
6328
|
+
throw new Error(`Runtime Event Consumer Checkpoint cannot advance from sequence ${current.sequence} to ${event.sequence}.`);
|
|
6329
|
+
}
|
|
6330
|
+
const updatedAt = createTimestamp();
|
|
6331
|
+
this.database.run(
|
|
6332
|
+
`INSERT INTO runtime_event_consumers (consumer_id, sequence, event_id, updated_at)
|
|
6333
|
+
VALUES (?, ?, ?, ?)
|
|
6334
|
+
ON CONFLICT(consumer_id) DO UPDATE SET
|
|
6335
|
+
sequence = excluded.sequence,
|
|
6336
|
+
event_id = excluded.event_id,
|
|
6337
|
+
updated_at = excluded.updated_at`,
|
|
6338
|
+
[consumerId, event.sequence, event.id, updatedAt]
|
|
6339
|
+
);
|
|
6340
|
+
return this.eventCheckpoint(consumerId);
|
|
6341
|
+
}
|
|
6342
|
+
insertMessage(message) {
|
|
6343
|
+
this.database.run(
|
|
6344
|
+
"INSERT INTO runtime_messages (id, agent_id, kind, payload_json, state, attempts, run_id, dead_letter_reason, created_at, updated_at) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)",
|
|
6345
|
+
[message.id, message.agentId, message.kind, json(message.input), message.state, message.attempts, message.runId ?? null, null, message.createdAt, message.updatedAt]
|
|
6346
|
+
);
|
|
6347
|
+
}
|
|
6348
|
+
agentFromRow(row) {
|
|
6349
|
+
return {
|
|
6350
|
+
id: row.id,
|
|
6351
|
+
sessionId: row.session_id,
|
|
6352
|
+
state: row.state,
|
|
6353
|
+
createdAt: row.created_at,
|
|
6354
|
+
updatedAt: row.updated_at
|
|
6355
|
+
};
|
|
6356
|
+
}
|
|
6357
|
+
messageFromRow(row) {
|
|
6358
|
+
const message = {
|
|
6359
|
+
id: row.id,
|
|
6360
|
+
agentId: row.agent_id,
|
|
6361
|
+
kind: row.kind,
|
|
6362
|
+
input: parse(row.payload_json),
|
|
6363
|
+
state: row.state,
|
|
6364
|
+
attempts: row.attempts,
|
|
6365
|
+
...row.run_id ? { runId: row.run_id } : {},
|
|
6366
|
+
...row.dead_letter_reason ? { deadLetterReason: row.dead_letter_reason } : {},
|
|
6367
|
+
createdAt: row.created_at,
|
|
6368
|
+
updatedAt: row.updated_at
|
|
6369
|
+
};
|
|
6370
|
+
if (row.lease_id && row.lease_run_id && row.worker_id && row.leased_at && row.expires_at) {
|
|
6371
|
+
message.lease = {
|
|
6372
|
+
id: row.lease_id,
|
|
6373
|
+
runId: row.lease_run_id,
|
|
6374
|
+
workerId: row.worker_id,
|
|
6375
|
+
leasedAt: row.leased_at,
|
|
6376
|
+
expiresAt: row.expires_at
|
|
6377
|
+
};
|
|
6378
|
+
}
|
|
6379
|
+
return message;
|
|
6380
|
+
}
|
|
6381
|
+
runFromRow(row) {
|
|
6382
|
+
return {
|
|
6383
|
+
id: row.id,
|
|
6384
|
+
agentId: row.agent_id,
|
|
6385
|
+
messageId: row.message_id,
|
|
6386
|
+
state: row.state,
|
|
6387
|
+
attempt: row.attempt,
|
|
6388
|
+
...row.lease_id ? { leaseId: row.lease_id } : {},
|
|
6389
|
+
...row.outcome_json ? { outcome: parse(row.outcome_json) } : {},
|
|
6390
|
+
...row.suspension_reason ? { suspensionReason: row.suspension_reason } : {},
|
|
6391
|
+
...row.input_request_json ? { inputRequest: parse(row.input_request_json) } : {},
|
|
6392
|
+
...row.execution_state_json ? { executionState: parse(row.execution_state_json) } : {},
|
|
6393
|
+
...row.metadata_json ? { metadata: parse(row.metadata_json) } : {},
|
|
6394
|
+
createdAt: row.created_at,
|
|
6395
|
+
updatedAt: row.updated_at
|
|
6396
|
+
};
|
|
6397
|
+
}
|
|
6398
|
+
eventFromRow(row) {
|
|
6399
|
+
return {
|
|
6400
|
+
id: row.id,
|
|
6401
|
+
sequence: row.sequence,
|
|
6402
|
+
kind: row.kind,
|
|
6403
|
+
...row.agent_id ? { agentId: row.agent_id } : {},
|
|
6404
|
+
...row.message_id ? { messageId: row.message_id } : {},
|
|
6405
|
+
...row.run_id ? { runId: row.run_id } : {},
|
|
6406
|
+
...row.tool_call_id ? { toolCallId: row.tool_call_id } : {},
|
|
6407
|
+
...row.payload_json ? { payload: optionalJson(row.payload_json) } : {},
|
|
6408
|
+
createdAt: row.created_at
|
|
6409
|
+
};
|
|
6410
|
+
}
|
|
6411
|
+
toolCallFromRow(row) {
|
|
6412
|
+
return {
|
|
6413
|
+
id: row.id,
|
|
6414
|
+
agentId: row.agent_id,
|
|
6415
|
+
runId: row.run_id,
|
|
6416
|
+
name: row.name,
|
|
6417
|
+
args: parse(row.args_json),
|
|
6418
|
+
state: row.state,
|
|
6419
|
+
...row.result_json ? { result: parse(row.result_json) } : {},
|
|
6420
|
+
...row.indeterminate_reason ? { indeterminateReason: row.indeterminate_reason } : {},
|
|
6421
|
+
createdAt: row.created_at,
|
|
6422
|
+
updatedAt: row.updated_at
|
|
6423
|
+
};
|
|
6424
|
+
}
|
|
6425
|
+
messageRow(messageId) {
|
|
6426
|
+
return this.database.query(`
|
|
6427
|
+
SELECT
|
|
6428
|
+
m.*,
|
|
6429
|
+
l.id AS lease_id,
|
|
6430
|
+
l.run_id AS lease_run_id,
|
|
6431
|
+
l.worker_id,
|
|
6432
|
+
l.leased_at,
|
|
6433
|
+
l.expires_at
|
|
6434
|
+
FROM runtime_messages m
|
|
6435
|
+
LEFT JOIN agent_runs r ON r.id = m.run_id
|
|
6436
|
+
LEFT JOIN runtime_leases l ON l.id = r.lease_id
|
|
6437
|
+
WHERE m.id = ?
|
|
6438
|
+
`).get(messageId);
|
|
6439
|
+
}
|
|
6440
|
+
runRow(runId) {
|
|
6441
|
+
return this.database.query("SELECT * FROM agent_runs WHERE id = ?").get(runId);
|
|
6442
|
+
}
|
|
6443
|
+
requireAgent(agentId) {
|
|
6444
|
+
const row = this.database.query("SELECT * FROM agents WHERE id = ?").get(agentId);
|
|
6445
|
+
if (!row) {
|
|
6446
|
+
throw new Error(`Agent not found: ${agentId}.`);
|
|
6447
|
+
}
|
|
6448
|
+
return this.agentFromRow(row);
|
|
6449
|
+
}
|
|
6450
|
+
requireMessage(messageId) {
|
|
6451
|
+
const row = this.messageRow(messageId);
|
|
6452
|
+
if (!row) {
|
|
6453
|
+
throw new Error(`Runtime Message not found: ${messageId}.`);
|
|
6454
|
+
}
|
|
6455
|
+
return this.messageFromRow(row);
|
|
6456
|
+
}
|
|
6457
|
+
requireRun(runId) {
|
|
6458
|
+
const row = this.runRow(runId);
|
|
6459
|
+
if (!row) {
|
|
6460
|
+
throw new Error(`Agent Run not found: ${runId}.`);
|
|
6461
|
+
}
|
|
6462
|
+
return this.runFromRow(row);
|
|
6463
|
+
}
|
|
6464
|
+
requireToolCall(toolCallId) {
|
|
6465
|
+
const row = this.database.query("SELECT * FROM runtime_tool_calls WHERE id = ?").get(toolCallId);
|
|
6466
|
+
if (!row) {
|
|
6467
|
+
throw new Error(`Tool Call not found: ${toolCallId}.`);
|
|
6468
|
+
}
|
|
6469
|
+
return this.toolCallFromRow(row);
|
|
6470
|
+
}
|
|
6471
|
+
requireEvent(eventId) {
|
|
6472
|
+
const row = this.database.query("SELECT * FROM runtime_events WHERE id = ?").get(eventId);
|
|
6473
|
+
if (!row) {
|
|
6474
|
+
throw new Error(`Runtime Event not found: ${eventId}.`);
|
|
6475
|
+
}
|
|
6476
|
+
return this.eventFromRow(row);
|
|
6477
|
+
}
|
|
6478
|
+
eventCheckpoint(consumerId) {
|
|
6479
|
+
const row = this.database.query(
|
|
6480
|
+
"SELECT consumer_id, sequence, event_id, updated_at FROM runtime_event_consumers WHERE consumer_id = ?"
|
|
6481
|
+
).get(consumerId);
|
|
6482
|
+
if (!row) {
|
|
6483
|
+
return {
|
|
6484
|
+
consumerId,
|
|
6485
|
+
sequence: 0,
|
|
6486
|
+
updatedAt: createTimestamp(/* @__PURE__ */ new Date(0))
|
|
6487
|
+
};
|
|
6488
|
+
}
|
|
6489
|
+
return {
|
|
6490
|
+
consumerId: row.consumer_id,
|
|
6491
|
+
sequence: row.sequence,
|
|
6492
|
+
...row.event_id ? { eventId: row.event_id } : {},
|
|
6493
|
+
updatedAt: row.updated_at
|
|
6494
|
+
};
|
|
6495
|
+
}
|
|
6496
|
+
recordEventInternal(kind, related, payload) {
|
|
6497
|
+
this.database.run(
|
|
6498
|
+
"INSERT INTO runtime_events (id, sequence, kind, agent_id, message_id, run_id, tool_call_id, payload_json, created_at) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)",
|
|
6499
|
+
[
|
|
6500
|
+
createId("evt"),
|
|
6501
|
+
(this.database.query("SELECT COALESCE(MAX(sequence), 0) AS sequence FROM runtime_events").get().sequence ?? 0) + 1,
|
|
6502
|
+
kind,
|
|
6503
|
+
related.agentId ?? null,
|
|
6504
|
+
related.messageId ?? null,
|
|
6505
|
+
related.runId ?? null,
|
|
6506
|
+
related.toolCallId ?? null,
|
|
6507
|
+
payload === void 0 ? null : json(payload),
|
|
6508
|
+
createTimestamp()
|
|
6509
|
+
]
|
|
6510
|
+
);
|
|
6511
|
+
}
|
|
6512
|
+
};
|
|
4489
6513
|
export {
|
|
4490
6514
|
Agent,
|
|
4491
|
-
|
|
4492
|
-
|
|
4493
|
-
|
|
4494
|
-
|
|
4495
|
-
|
|
4496
|
-
|
|
4497
|
-
appendUserTurn,
|
|
4498
|
-
buildModelRequest,
|
|
4499
|
-
buildSystemPrompt,
|
|
4500
|
-
conversationMessages,
|
|
6515
|
+
AgentRun,
|
|
6516
|
+
AgentRuntime,
|
|
6517
|
+
InMemoryRuntimeStateStore,
|
|
6518
|
+
InMemorySessionStore,
|
|
6519
|
+
JsonlSessionStore,
|
|
6520
|
+
SqliteRuntimeStateStore,
|
|
4501
6521
|
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
|
|
6522
|
+
createMessage
|
|
4529
6523
|
};
|