glove-foundry 0.4.0 → 0.5.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.
@@ -1,29 +1,25 @@
1
1
  import {
2
2
  DEFAULT_FOUNDRY_CONFIG
3
3
  } from "./chunk-ZFNMFE3T.js";
4
+ import {
5
+ executionEvents
6
+ } from "./chunk-J7ZBJM54.js";
4
7
  import {
5
8
  EMPTY_AGENT_COMPOSITION,
6
9
  EMPTY_CAPABILITY_REGISTRY,
7
10
  EMPTY_FOUNDRY_APPLICATION,
8
11
  EMPTY_NATIVE_REGISTRY,
9
12
  FOUNDRY_AGENT_APPLICATION_BRAND,
10
- FOUNDRY_AGENT_FILE_ENV,
11
- FOUNDRY_AGENT_ROUTE_ENV,
12
- FOUNDRY_APPLICATION_ENV,
13
13
  FOUNDRY_APPROVAL_DIRECTORY_ENV,
14
14
  FOUNDRY_CORE_COMMAND_DIRECTORY_ENV,
15
15
  FOUNDRY_CORE_COMMAND_EVENT,
16
- FOUNDRY_EVENT_PREFIX,
17
- FOUNDRY_EXECUTION_MARKER,
18
16
  FOUNDRY_LAYER_BRAND,
19
17
  FOUNDRY_MCP_BRAND,
20
18
  FOUNDRY_MEMORY_BRAND,
21
19
  FOUNDRY_SHARED_TOOL_BRAND,
22
20
  FOUNDRY_SUBSCRIBER_BRAND,
23
21
  MemoryFoundryDataAdapter,
24
- bindFileIdentity,
25
22
  claimFoundryCoreCommandRequest,
26
- compileAgentDefinition,
27
23
  createAgentInstance,
28
24
  createConversation,
29
25
  createManifest,
@@ -40,10 +36,15 @@ import {
40
36
  reconstructPlaybook,
41
37
  reconstructPlaybookSubscription,
42
38
  resolveFoundryApproval,
43
- routeFromInternalAgentName,
44
39
  settleFoundryCoreCommandRequest,
45
40
  transmissionPredicate
46
- } from "./chunk-5LDW2N2E.js";
41
+ } from "./chunk-KBMD4RYU.js";
42
+ import {
43
+ FOUNDRY_APPLICATION_ENV,
44
+ FOUNDRY_EVENT_PREFIX,
45
+ FOUNDRY_EXECUTION_MARKER,
46
+ bindFileIdentity
47
+ } from "./chunk-5NFLGHOC.js";
47
48
 
48
49
  // src/codegen.ts
49
50
  import { mkdir, writeFile } from "node:fs/promises";
@@ -837,27 +838,189 @@ var FoundryObserver = class {
837
838
  };
838
839
 
839
840
  // src/runtime.ts
840
- import { existsSync } from "node:fs";
841
- import { mkdir as mkdir2, rm } from "node:fs/promises";
841
+ import { mkdir as mkdir2, rm as rm2 } from "node:fs/promises";
842
842
  import { randomUUID as randomUUID2 } from "node:crypto";
843
- import { tmpdir } from "node:os";
844
- import { join } from "node:path";
845
- import { fileURLToPath } from "node:url";
843
+ import { tmpdir as tmpdir2 } from "node:os";
844
+ import { join as join2 } from "node:path";
846
845
  import {
847
846
  Effect as Effect5,
848
847
  Layer as Layer3,
849
848
  ManagedRuntime as EffectManagedRuntime,
850
849
  Schema as Schema3
851
850
  } from "effect";
852
- import { EnvStore, MemoryEnvStorage } from "station-env";
853
- import {
854
- MemoryAdapter,
855
- parseInterval,
856
- SignalRunner
857
- } from "station-signal";
851
+
852
+ // src/execution-backend.ts
853
+ import { fork } from "node:child_process";
854
+ import { existsSync } from "node:fs";
855
+ import { mkdtemp, rm } from "node:fs/promises";
856
+ import { tmpdir } from "node:os";
857
+ import { join } from "node:path";
858
+ import { fileURLToPath } from "node:url";
859
+ function entry(name) {
860
+ const built = fileURLToPath(new URL(`./${name}.js`, import.meta.url));
861
+ return existsSync(built) ? built : fileURLToPath(new URL(`./${name}.ts`, import.meta.url));
862
+ }
863
+ var FoundryExecutionBackend = class {
864
+ constructor(options) {
865
+ this.options = options;
866
+ }
867
+ child;
868
+ directory;
869
+ sequence = 0;
870
+ stopped = false;
871
+ stopping;
872
+ pending = /* @__PURE__ */ new Map();
873
+ loopResolve;
874
+ loopReject;
875
+ loop;
876
+ get processId() {
877
+ return this.child?.pid;
878
+ }
879
+ async initialize() {
880
+ if (this.child || this.stopped) throw new Error("Execution backend cannot be initialized twice.");
881
+ this.directory = await mkdtemp(join(tmpdir(), "glove-foundry-daemon-"));
882
+ const env = { ...process.env, ...this.options.env };
883
+ for (const name of ["NODE_OPTIONS", "LD_PRELOAD", "LD_LIBRARY_PATH", "DYLD_INSERT_LIBRARIES"]) delete env[name];
884
+ const child = this.child = fork(entry("execution-daemon"), [], {
885
+ cwd: this.options.rootDir,
886
+ env,
887
+ serialization: "advanced",
888
+ execArgv: ["--import", import.meta.resolve("tsx")],
889
+ stdio: ["ignore", "ignore", "pipe", "ipc"]
890
+ });
891
+ child.stderr?.resume();
892
+ this.loop = new Promise((resolve3, reject) => {
893
+ this.loopResolve = resolve3;
894
+ this.loopReject = reject;
895
+ });
896
+ void this.loop.catch(() => {
897
+ });
898
+ child.on("message", (message) => {
899
+ if ("event" in message) {
900
+ if (executionEvents.includes(message.event)) {
901
+ const handler = this.options.observer[message.event];
902
+ handler.call(this.options.observer, message.value);
903
+ }
904
+ return;
905
+ }
906
+ const request = this.pending.get(message.id);
907
+ if (!request) return;
908
+ clearTimeout(request.timer);
909
+ this.pending.delete(message.id);
910
+ if (message.error) request.reject(new Error(message.error));
911
+ else request.resolve(message.value);
912
+ });
913
+ const failed = () => {
914
+ const error = new Error("Foundry execution daemon disconnected. A pending mutation may have completed; inspect state before retrying.");
915
+ for (const request of this.pending.values()) {
916
+ clearTimeout(request.timer);
917
+ request.reject(error);
918
+ }
919
+ this.pending.clear();
920
+ if (this.stopped) this.loopResolve?.();
921
+ else this.loopReject?.(error);
922
+ };
923
+ child.once("error", failed);
924
+ child.once("exit", failed);
925
+ child.once("disconnect", failed);
926
+ try {
927
+ await this.request({ method: "init", options: {
928
+ rootDir: this.options.rootDir,
929
+ directory: this.directory,
930
+ loader: entry("execution-loader"),
931
+ agents: this.options.agents,
932
+ runner: this.options.runner
933
+ } }, 6e4);
934
+ } catch (error) {
935
+ await this.stop();
936
+ throw error;
937
+ }
938
+ }
939
+ request(command, timeout = 3e4) {
940
+ if (!this.child?.connected || this.stopped && command.method !== "stop") return Promise.reject(new Error("Execution daemon is unavailable."));
941
+ const id2 = ++this.sequence;
942
+ return new Promise((resolve3, reject) => {
943
+ const timer = setTimeout(() => {
944
+ this.pending.delete(id2);
945
+ reject(new Error("Execution daemon request timed out. Its outcome may be unknown; do not automatically retry mutations."));
946
+ }, timeout);
947
+ this.pending.set(id2, { resolve: (value) => resolve3(value), reject, timer });
948
+ this.child.send({ id: id2, command }, (error) => {
949
+ if (error) {
950
+ clearTimeout(timer);
951
+ this.pending.delete(id2);
952
+ reject(new Error("Execution daemon request could not be delivered."));
953
+ }
954
+ });
955
+ });
956
+ }
957
+ start() {
958
+ return this.loop ?? Promise.reject(new Error("Execution backend is not initialized."));
959
+ }
960
+ stop(_options) {
961
+ if (this.stopping) return this.stopping;
962
+ this.stopped = true;
963
+ this.stopping = (async () => {
964
+ const child = this.child;
965
+ try {
966
+ if (child) {
967
+ try {
968
+ if (child.connected) await this.request({ method: "stop" }, 15e3);
969
+ } finally {
970
+ if (child.exitCode === null && child.signalCode === null) {
971
+ await new Promise((resolve3) => {
972
+ const timer = setTimeout(() => child.kill("SIGKILL"), 5e3);
973
+ child.once("exit", () => {
974
+ clearTimeout(timer);
975
+ resolve3();
976
+ });
977
+ if (child.connected) child.disconnect();
978
+ });
979
+ }
980
+ }
981
+ }
982
+ } finally {
983
+ if (this.directory) await rm(this.directory, { recursive: true, force: true });
984
+ }
985
+ })();
986
+ return this.stopping;
987
+ }
988
+ triggerSignal(name, input) {
989
+ return this.request({ method: "trigger", name, input });
990
+ }
991
+ getRun(id2) {
992
+ return this.request({ method: "get", id: id2 });
993
+ }
994
+ listAllRuns(options = {}) {
995
+ return this.request({ method: "list", ...options });
996
+ }
997
+ cancel(id2) {
998
+ return this.request({ method: "cancel", id: id2 });
999
+ }
1000
+ ping() {
1001
+ return this.request({ method: "ping" });
1002
+ }
1003
+ getAdapter() {
1004
+ return { ping: () => this.ping() };
1005
+ }
1006
+ schedules = {
1007
+ add: (schedule) => this.request({ method: "schedule.add", schedule }),
1008
+ delete: (id2) => this.request({ method: "schedule.delete", id: id2 }),
1009
+ ping: () => this.ping()
1010
+ };
1011
+ async waitForRun(id2, options = {}) {
1012
+ const deadline = Date.now() + (options.timeoutMs ?? 6e4);
1013
+ do {
1014
+ const run = await this.getRun(id2);
1015
+ if (!run || ["completed", "failed", "cancelled"].includes(run.status)) return run;
1016
+ await new Promise((resolve3) => setTimeout(resolve3, options.pollMs ?? 100));
1017
+ } while (Date.now() < deadline);
1018
+ return this.getRun(id2);
1019
+ }
1020
+ };
1021
+
1022
+ // src/runtime.ts
858
1023
  import {
859
- ScheduleMemoryAdapter,
860
- ScheduleReconciler,
861
1024
  nextCronOccurrence
862
1025
  } from "station-schedules";
863
1026
 
@@ -1077,11 +1240,11 @@ async function moduleFiles(directory, suffix) {
1077
1240
  if (code === "ENOENT") return [];
1078
1241
  throw cause;
1079
1242
  }
1080
- return entries.filter((entry) => {
1081
- if (entry.endsWith(".d.ts")) return false;
1082
- const extension = extname(entry);
1083
- return MODULE_EXTENSIONS.has(extension) && entry.slice(0, -extension.length).endsWith(suffix);
1084
- }).map((entry) => resolve2(directory, entry)).sort();
1243
+ return entries.filter((entry2) => {
1244
+ if (entry2.endsWith(".d.ts")) return false;
1245
+ const extension = extname(entry2);
1246
+ return MODULE_EXTENSIONS.has(extension) && entry2.slice(0, -extension.length).endsWith(suffix);
1247
+ }).map((entry2) => resolve2(directory, entry2)).sort();
1085
1248
  }
1086
1249
  function routeFromModule(directory, filePath, suffix) {
1087
1250
  const rel = normalizePath(relative2(directory, filePath));
@@ -1281,11 +1444,6 @@ function promiseEffect(operation, evaluate) {
1281
1444
  catch: (cause) => runtimeFailure(operation, cause)
1282
1445
  }).pipe(Effect5.withSpan(`foundry.${operation}`));
1283
1446
  }
1284
- function executionAgentEntrypoint() {
1285
- const built = fileURLToPath(new URL("./execution-agent.js", import.meta.url));
1286
- if (existsSync(built)) return built;
1287
- return fileURLToPath(new URL("./execution-agent.ts", import.meta.url));
1288
- }
1289
1447
  var FoundryRuntime = class _FoundryRuntime {
1290
1448
  rootDir;
1291
1449
  agents;
@@ -1306,20 +1464,21 @@ var FoundryRuntime = class _FoundryRuntime {
1306
1464
  execution;
1307
1465
  observer;
1308
1466
  signalRunner;
1309
- envStore;
1310
- envProvider;
1311
1467
  scheduleAdapter;
1312
1468
  services;
1313
1469
  runnerLoops = [];
1314
1470
  materializedActivations = /* @__PURE__ */ new Set();
1471
+ // Serialize host-owned activation transitions across asynchronous adapters and daemon IPC.
1472
+ activationTransitions = Effect5.unsafeMakeSemaphore(1);
1473
+ activationDispatches = /* @__PURE__ */ new Map();
1315
1474
  activationTimers = /* @__PURE__ */ new Map();
1316
- approvalDirectory = join(
1317
- tmpdir(),
1475
+ approvalDirectory = join2(
1476
+ tmpdir2(),
1318
1477
  "glove-foundry-approvals",
1319
1478
  randomUUID2()
1320
1479
  );
1321
- coreCommandDirectory = join(
1322
- tmpdir(),
1480
+ coreCommandDirectory = join2(
1481
+ tmpdir2(),
1323
1482
  "glove-foundry-core-commands",
1324
1483
  randomUUID2()
1325
1484
  );
@@ -1440,63 +1599,21 @@ var FoundryRuntime = class _FoundryRuntime {
1440
1599
  pollIntervalMs: configured?.pollIntervalMs ?? defaults.pollIntervalMs,
1441
1600
  idlePollIntervalMs: configured?.idlePollIntervalMs ?? defaults.idlePollIntervalMs
1442
1601
  };
1443
- this.envStore = new EnvStore(new MemoryEnvStorage());
1444
- this.envProvider = {
1445
- resolveFor: async (target) => {
1446
- const resolvedEnvironment = await this.envStore.resolveFor(target);
1447
- if (target.kind === "signal") {
1448
- const route = routeFromInternalAgentName(target.name);
1449
- resolvedEnvironment[FOUNDRY_AGENT_ROUTE_ENV] = route;
1450
- const discovered = this.byRoute.get(route);
1451
- if (discovered) {
1452
- resolvedEnvironment[FOUNDRY_AGENT_FILE_ENV] = discovered.filePath;
1453
- }
1454
- resolvedEnvironment[FOUNDRY_APPROVAL_DIRECTORY_ENV] = this.approvalDirectory;
1455
- resolvedEnvironment[FOUNDRY_CORE_COMMAND_DIRECTORY_ENV] = this.coreCommandDirectory;
1456
- }
1457
- if (this.applicationFilePath) {
1458
- resolvedEnvironment[FOUNDRY_APPLICATION_ENV] = this.applicationFilePath;
1459
- }
1460
- return resolvedEnvironment;
1602
+ if (this.application.daemon && !this.applicationFilePath) {
1603
+ throw new Error("A daemon adapter requires applicationFilePath so Foundry can load it in the managed process.");
1604
+ }
1605
+ this.signalRunner = new FoundryExecutionBackend({
1606
+ rootDir: this.rootDir,
1607
+ agents: this.agents.map((agent) => ({ route: agent.route, filePath: agent.filePath })),
1608
+ runner: this.execution,
1609
+ observer: this.observer,
1610
+ env: {
1611
+ [FOUNDRY_APPROVAL_DIRECTORY_ENV]: this.approvalDirectory,
1612
+ [FOUNDRY_CORE_COMMAND_DIRECTORY_ENV]: this.coreCommandDirectory,
1613
+ ...this.applicationFilePath ? { [FOUNDRY_APPLICATION_ENV]: this.applicationFilePath } : {}
1461
1614
  }
1462
- };
1463
- this.scheduleAdapter = new ScheduleMemoryAdapter();
1464
- const signalAdapter = new MemoryAdapter();
1465
- const signalScheduleReconciler = new ScheduleReconciler({
1466
- adapter: this.scheduleAdapter,
1467
- kinds: ["signal"],
1468
- triggerFn: (schedule, scheduledFor) => this.signalRunner.triggerSignal(
1469
- schedule.target,
1470
- schedule.input,
1471
- { id: schedule.id, scheduledFor }
1472
- ),
1473
- hasPendingOrRunning: (schedule) => this.signalRunner.hasPendingOrRunningForSignal(schedule.target),
1474
- parseInterval,
1475
- onError: (error, schedule) => this.observability.append({
1476
- type: "schedule.error",
1477
- category: "activation",
1478
- data: { scheduleId: schedule?.id, error: error.message }
1479
- })
1480
- });
1481
- this.signalRunner = new SignalRunner({
1482
- adapter: signalAdapter,
1483
- pollIntervalMs: this.execution.pollIntervalMs,
1484
- idlePollIntervalMs: this.execution.idlePollIntervalMs,
1485
- maxConcurrent: this.execution.maxConcurrent,
1486
- maxAttempts: this.execution.maxAttempts,
1487
- retryBackoffMs: this.execution.retryBackoffMs,
1488
- subscribers: [this.observer],
1489
- scheduleReconciler: signalScheduleReconciler,
1490
- envProvider: this.envProvider,
1491
- stationId: "foundry-local",
1492
- failUnknownSignals: true
1493
1615
  });
1494
- for (const discovered of this.agents) {
1495
- this.signalRunner.registerSignal(
1496
- this.executionSignal(discovered),
1497
- executionAgentEntrypoint()
1498
- );
1499
- }
1616
+ this.scheduleAdapter = this.signalRunner.schedules;
1500
1617
  const topologyLayer = memoryTopologyStore;
1501
1618
  const defaultServices = Layer3.mergeAll(
1502
1619
  memoryAccountDirectory(this.application.accounts ?? []),
@@ -1586,26 +1703,25 @@ var FoundryRuntime = class _FoundryRuntime {
1586
1703
  if (!this.started) {
1587
1704
  await this.services.dispose();
1588
1705
  await Promise.all([
1589
- rm(this.approvalDirectory, { recursive: true, force: true }),
1590
- rm(this.coreCommandDirectory, { recursive: true, force: true })
1706
+ rm2(this.approvalDirectory, { recursive: true, force: true }),
1707
+ rm2(this.coreCommandDirectory, { recursive: true, force: true })
1591
1708
  ]);
1592
1709
  this.disposed = true;
1593
1710
  return;
1594
1711
  }
1595
1712
  for (const timer of this.activationTimers.values()) clearTimeout(timer);
1596
1713
  this.activationTimers.clear();
1714
+ this.activationDispatches.clear();
1597
1715
  await this.connectionSupervisor.stopAll();
1598
1716
  this.observability.append({ type: "runtime.stop.signals", category: "system", data: {} });
1599
1717
  await this.signalRunner.stop({ graceful: true, timeoutMs: 1e4 });
1600
1718
  this.observability.append({ type: "runtime.stop.loops", category: "system", data: {} });
1601
1719
  await this.settleRunnerLoops(1e3);
1602
1720
  this.runnerLoops.length = 0;
1603
- await this.envStore.close();
1604
- await this.scheduleAdapter.close?.();
1605
1721
  await this.services.dispose();
1606
1722
  await Promise.all([
1607
- rm(this.approvalDirectory, { recursive: true, force: true }),
1608
- rm(this.coreCommandDirectory, { recursive: true, force: true })
1723
+ rm2(this.approvalDirectory, { recursive: true, force: true }),
1724
+ rm2(this.coreCommandDirectory, { recursive: true, force: true })
1609
1725
  ]);
1610
1726
  this.started = false;
1611
1727
  this.disposed = true;
@@ -1864,9 +1980,9 @@ var FoundryRuntime = class _FoundryRuntime {
1864
1980
  return Effect5.runPromise(this.data.listWorkspaceEntries(workspaceId));
1865
1981
  }
1866
1982
  async putWorkspaceEntry(workspaceId, key, value) {
1867
- const entry = { workspaceId, key, value, updatedAt: (/* @__PURE__ */ new Date()).toISOString() };
1868
- await Effect5.runPromise(this.data.putWorkspaceEntry(entry));
1869
- return entry;
1983
+ const entry2 = { workspaceId, key, value, updatedAt: (/* @__PURE__ */ new Date()).toISOString() };
1984
+ await Effect5.runPromise(this.data.putWorkspaceEntry(entry2));
1985
+ return entry2;
1870
1986
  }
1871
1987
  listSharedInbox(workspaceId) {
1872
1988
  return Effect5.runPromise(this.data.listInboxItems(workspaceId));
@@ -2431,7 +2547,7 @@ var FoundryRuntime = class _FoundryRuntime {
2431
2547
  async health() {
2432
2548
  const [execution, environment, activations] = await Promise.all([
2433
2549
  this.signalRunner.getAdapter().ping(),
2434
- this.envStore.ping(),
2550
+ this.signalRunner.ping(),
2435
2551
  this.scheduleAdapter.ping()
2436
2552
  ]);
2437
2553
  return {
@@ -2867,12 +2983,6 @@ var FoundryRuntime = class _FoundryRuntime {
2867
2983
  }
2868
2984
  }
2869
2985
  }
2870
- executionSignal(discovered) {
2871
- return compileAgentDefinition(
2872
- discovered.definition,
2873
- discovered.route
2874
- );
2875
- }
2876
2986
  async seedTopology() {
2877
2987
  for (const route of this.application.routes ?? []) {
2878
2988
  await this.putRoute(route);
@@ -2920,7 +3030,19 @@ var FoundryRuntime = class _FoundryRuntime {
2920
3030
  await this.executionEnvelope(request)
2921
3031
  );
2922
3032
  }
3033
+ withActivationTransition(work) {
3034
+ return Effect5.runPromise(this.activationTransitions.withPermits(1)(Effect5.tryPromise({
3035
+ try: work,
3036
+ catch: (cause) => cause
3037
+ })));
3038
+ }
2923
3039
  async executeCoreCommand(command, parentRunId) {
3040
+ if (command.type === "sleep" || command.type === "schedule" || command.type.startsWith("schedule.")) {
3041
+ return this.withActivationTransition(() => this.executeCoreCommandUnlocked(command, parentRunId));
3042
+ }
3043
+ return this.executeCoreCommandUnlocked(command, parentRunId);
3044
+ }
3045
+ async executeCoreCommandUnlocked(command, parentRunId) {
2924
3046
  if (command.type === "transmit") {
2925
3047
  const request2 = await getFoundryCoreCommandRequest(
2926
3048
  this.coreCommandDirectory,
@@ -3126,6 +3248,7 @@ var FoundryRuntime = class _FoundryRuntime {
3126
3248
  return `activation/${activationId.replaceAll("_", "-")}`;
3127
3249
  }
3128
3250
  async disarmActivation(activation) {
3251
+ this.activationDispatches.delete(activation.id);
3129
3252
  const timer = this.activationTimers.get(activation.id);
3130
3253
  if (timer) {
3131
3254
  clearTimeout(timer);
@@ -3279,11 +3402,13 @@ var FoundryRuntime = class _FoundryRuntime {
3279
3402
  data: { commandId: command.id, activationId: existing.id }
3280
3403
  });
3281
3404
  }
3282
- async reconstructActivations() {
3283
- for (const activation of await Effect5.runPromise(this.data.listActivations())) {
3284
- if (activation.status === "paused" || activation.status === "completed" || activation.status === "cancelled") continue;
3285
- await this.materializeActivation(activation);
3286
- }
3405
+ reconstructActivations() {
3406
+ return this.withActivationTransition(async () => {
3407
+ for (const activation of await Effect5.runPromise(this.data.listActivations())) {
3408
+ if (activation.status === "paused" || activation.status === "completed" || activation.status === "cancelled") continue;
3409
+ await this.materializeActivation(activation);
3410
+ }
3411
+ });
3287
3412
  }
3288
3413
  async materializeActivation(activation) {
3289
3414
  if (this.materializedActivations.has(activation.id)) return;
@@ -3373,7 +3498,11 @@ var FoundryRuntime = class _FoundryRuntime {
3373
3498
  updatedAt: (/* @__PURE__ */ new Date()).toISOString()
3374
3499
  };
3375
3500
  await Effect5.runPromise(this.data.putActivation(active));
3376
- const dispatch = async () => {
3501
+ const generation = {};
3502
+ this.activationDispatches.set(activation.id, generation);
3503
+ const dispatch = () => this.withActivationTransition(async () => {
3504
+ if (this.activationDispatches.get(activation.id) !== generation) return;
3505
+ this.activationDispatches.delete(activation.id);
3377
3506
  this.activationTimers.delete(activation.id);
3378
3507
  if (this.disposed || !this.started) return;
3379
3508
  const current = await Effect5.runPromise(this.data.getActivation(activation.id));
@@ -3393,7 +3522,7 @@ var FoundryRuntime = class _FoundryRuntime {
3393
3522
  runId: activation.createdByRunId,
3394
3523
  data: { commandId: activation.id, childRunId: runId, type: activation.kind }
3395
3524
  });
3396
- void this.waitForRun(runId).then(async (run) => {
3525
+ void this.waitForRun(runId).then((run) => this.withActivationTransition(async () => {
3397
3526
  if (!run || run.status !== "completed" && run.status !== "failed" && run.status !== "cancelled") return;
3398
3527
  const latest = await Effect5.runPromise(this.data.getActivation(activation.id));
3399
3528
  if (!latest || latest.status !== "active" || latest.lastRunId !== runId) return;
@@ -3402,7 +3531,7 @@ var FoundryRuntime = class _FoundryRuntime {
3402
3531
  status: run.status === "cancelled" ? "cancelled" : "completed",
3403
3532
  updatedAt: (/* @__PURE__ */ new Date()).toISOString()
3404
3533
  }));
3405
- }).catch((cause) => {
3534
+ })).catch((cause) => {
3406
3535
  this.observability.append({
3407
3536
  type: "activation.persistence.error",
3408
3537
  category: "activation",
@@ -3411,7 +3540,7 @@ var FoundryRuntime = class _FoundryRuntime {
3411
3540
  data: { error: cause instanceof Error ? cause.message : String(cause) }
3412
3541
  });
3413
3542
  });
3414
- };
3543
+ });
3415
3544
  const arm = () => {
3416
3545
  const remaining = target - Date.now();
3417
3546
  if (remaining <= 0) {
@@ -3437,8 +3566,7 @@ var FoundryRuntime = class _FoundryRuntime {
3437
3566
  runId: activation.createdByRunId,
3438
3567
  data: { commandId: activation.id, recurring: false, nextRunAt: activation.timing.at }
3439
3568
  });
3440
- if (target <= Date.now()) await dispatch();
3441
- else arm();
3569
+ arm();
3442
3570
  }
3443
3571
  async deliverOutbound(command, parentRunId, signal) {
3444
3572
  return this.dispatchOutbound({