@rowan-agent/agent 0.5.5 → 0.6.0

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