@nylorun/runtime 0.40.0-beta → 0.41.0-beta

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 (41) hide show
  1. package/CHANGELOG.md +15 -0
  2. package/dist/adapters/streams/s2.js +9 -2
  3. package/dist/api/components.d.ts +131 -0
  4. package/dist/api/components.js +3 -1
  5. package/dist/api/http/app.js +2 -0
  6. package/dist/api/http/routes/runs.d.ts +8 -0
  7. package/dist/api/http/routes/runs.js +30 -0
  8. package/dist/api/http/routes/triggers.d.ts +2 -1
  9. package/dist/api/http/routes/triggers.js +32 -4
  10. package/dist/api/openapi.js +3 -1
  11. package/dist/gates/policy.d.ts +6 -4
  12. package/dist/gates/policy.js +10 -2
  13. package/dist/harness-api/record.js +51 -6
  14. package/dist/harness-api/start.js +15 -9
  15. package/dist/harness-api/tool-journal.js +3 -0
  16. package/dist/management-openapi.json +11 -6
  17. package/dist/openapi.json +506 -12
  18. package/dist/store/postgres/drizzle/0043_agent_triggers_origin.sql +11 -0
  19. package/dist/store/postgres/drizzle/0044_agent_triggers_origin_backfill.sql +8 -0
  20. package/dist/store/postgres/drizzle/meta/_journal.json +14 -0
  21. package/dist/store/postgres/schema.d.ts +151 -4
  22. package/dist/store/postgres/schema.js +25 -4
  23. package/dist/store/postgres/store.js +20 -2
  24. package/dist/store/types.d.ts +10 -0
  25. package/dist/streams/types.d.ts +16 -1
  26. package/dist/streams/types.js +8 -1
  27. package/dist/tenant/advance.js +7 -0
  28. package/dist/tenant/agent-triggers.d.ts +60 -0
  29. package/dist/tenant/agent-triggers.js +179 -0
  30. package/dist/tenant/agents.d.ts +1 -1
  31. package/dist/tenant/agents.js +5 -1
  32. package/dist/tenant/limits.d.ts +26 -7
  33. package/dist/tenant/limits.js +39 -6
  34. package/dist/tenant/runs-stream.d.ts +51 -0
  35. package/dist/tenant/runs-stream.js +164 -0
  36. package/dist/tenant/sweep.js +3 -0
  37. package/dist/tenant/triggers.d.ts +31 -5
  38. package/dist/tenant/triggers.js +168 -48
  39. package/dist/version.d.ts +1 -1
  40. package/dist/version.js +1 -1
  41. package/package.json +3 -3
@@ -25,15 +25,24 @@
25
25
  *
26
26
  * A schedule's runs are moved by a fixed offset (jitter), from a hash of the Agent and trigger id:
27
27
  * up to `triggerJitterPercent` of its interval and `triggerJitterMaxMs`.
28
+ *
29
+ * **Origins** (D83). A row's definition comes from where it was made (`origin`, `definitionOf`):
30
+ * `manifest`, the entry of the version the Agent runs; `agent`, the row's own (a trigger an agent
31
+ * made with the `triggers` tool, `agent-triggers.ts`); `wake`, a session's booked wake. Every
32
+ * origin fires through `fireTimer` and `deliverRun`; only where its definition comes from differs.
33
+ * An upgrade merges `manifest` rows only, and an `agent` row does not fire while the version lacks
34
+ * `harness.engine.triggers`: it is paused (`undeclared`) until a version declares it again.
28
35
  */
29
36
  import { createHash, randomBytes } from "node:crypto";
30
37
  import { Cron } from "croner";
31
38
  import { TIMER_MAX_MS, triggerKindOf, } from "@nylorun/core/contracts";
39
+ import { appendRuns } from "./runs-stream.js";
32
40
  import { hostTuning } from "../tuning.js";
33
41
  import { checkAgentTurn, liveEnvironment } from "./agents.js";
34
42
  import { acceptMessage, QUEUE_SCOPE, takesMessages } from "./commands.js";
35
43
  import { fail, HttpError } from "./http.js";
36
44
  import { readRuntimeLimits } from "./runtime-limits.js";
45
+ import { triggerLimitsInForce } from "./limits.js";
37
46
  import { openSessionIn } from "./sessions.js";
38
47
  const TIMER_PREFIX = "trigger:";
39
48
  const HOUR_MS = 3_600_000;
@@ -97,7 +106,9 @@ export function nextFireAt(agentId, triggerId, trigger, after, limits) {
97
106
  * refuses, two runs closer than `triggerMinIntervalMs` over the next `triggers.intervalCheckFires`
98
107
  * fires, an `at` that is not in the future, a prompt past `triggerPromptChars`.
99
108
  */
100
- export function triggerIssues(triggers, limits, now = new Date()) {
109
+ export function triggerIssues(triggers, limits, now = new Date(),
110
+ /** How an issue names trigger `id`: its place in the manifest by default. */
111
+ label = (id) => `runtime.triggers.${id}`) {
101
112
  if (!triggers)
102
113
  return [];
103
114
  const issues = [];
@@ -106,7 +117,7 @@ export function triggerIssues(triggers, limits, now = new Date()) {
106
117
  issues.push(`runtime.triggers declares ${ids.length} triggers, over the Runtime limit triggersPerAgent (${limits.triggersPerAgent}; PUT /v1/tenant/limits).`);
107
118
  const fires = hostTuning().triggers.intervalCheckFires;
108
119
  for (const [id, trigger] of Object.entries(triggers)) {
109
- const at = `runtime.triggers.${id}`;
120
+ const at = label(id);
110
121
  if (trigger.prompt.length > limits.triggerPromptChars)
111
122
  issues.push(`${at}.prompt is ${trigger.prompt.length} characters, over the Runtime limit triggerPromptChars (${limits.triggerPromptChars}).`);
112
123
  if (trigger.at !== undefined && Date.parse(trigger.at) <= now.getTime())
@@ -135,9 +146,35 @@ export function triggerIssues(triggers, limits, now = new Date()) {
135
146
  }
136
147
  /** The triggers of the manifest version an Agent runs. */
137
148
  async function triggersOf(t, agent) {
138
- const version = await t.agentManifest(agent.manifestName, agent.manifestVersion);
139
- return (version?.body?.runtime?.triggers ?? {});
149
+ return (await pinnedOf(t, agent)).triggers;
150
+ }
151
+ /** What the manifest version an Agent runs says of triggers: its own, and whether it declares the capability. */
152
+ async function pinnedOf(t, agent) {
153
+ const body = (await t.agentManifest(agent.manifestName, agent.manifestVersion))?.body;
154
+ const harness = body?.harness;
155
+ return { triggers: (body?.runtime?.triggers ?? {}), declared: harness?.engine?.triggers === true };
156
+ }
157
+ /** Where a row's definition comes from (D83): its origin, or `wake` for a booked wake written before origins. */
158
+ export function originOf(row) {
159
+ return row.kind === "wake" ? "wake" : row.origin;
160
+ }
161
+ /**
162
+ * The definition trigger row `row` fires with (D83): a booked wake's, from its session and note;
163
+ * an agent-made trigger's own, from the row; a manifest trigger's, the entry of the version the
164
+ * Agent runs (`manifest`, read once by the caller). Undefined when it has none.
165
+ */
166
+ export function definitionOf(row, manifest) {
167
+ switch (originOf(row)) {
168
+ case "wake":
169
+ return wakeTriggerOf(row);
170
+ case "agent":
171
+ return (row.definition ?? undefined);
172
+ default:
173
+ return manifest[row.triggerId];
174
+ }
140
175
  }
176
+ /** The columns a row of origin `manifest` or `wake` leaves empty. */
177
+ const NO_AGENT_FIELDS = { title: null, definition: null, createdSessionId: null, createdTurnId: null };
141
178
  /**
142
179
  * Writes the state of a new Agent's triggers (D74): every trigger of the version it is made from
143
180
  * starts active, with its next time. Arm them after the commit (`armAgentTriggers`).
@@ -161,6 +198,8 @@ export async function createAgentTriggers(t, agent, now = new Date()) {
161
198
  sessionId: null,
162
199
  note: null,
163
200
  effectId: null,
201
+ origin: "manifest",
202
+ ...NO_AGENT_FIELDS,
164
203
  createdAt: stamp,
165
204
  updatedAt: stamp,
166
205
  });
@@ -170,21 +209,41 @@ export async function createAgentTriggers(t, agent, now = new Date()) {
170
209
  * After Agent `agent` (locked) moved from manifest version `from` to the version it now runs (track
171
210
  * T3, D73), in the move's transaction: its triggers merge by id. One the new version adds arrives
172
211
  * paused (`new`), one it drops loses its state (its runs stay), and one whose timing changed gets a
173
- * new generation and next time; a changed prompt or session applies at the next fire. The sweep
174
- * arms what changed (`reconcileTriggers`).
212
+ * new generation and next time; a changed prompt or session applies at the next fire. Only rows of
213
+ * origin `manifest` merge (D83): an agent's and a booked wake stay as they are, but for an agent's
214
+ * paused `undeclared`, which resumes on a version that declares `harness.engine.triggers`. A
215
+ * manifest trigger whose id an agent's trigger holds is not added. The sweep arms what changed
216
+ * (`reconcileTriggers`).
175
217
  */
176
218
  export async function mergeAgentTriggers(t, agent, from, now = new Date()) {
177
219
  const before = ((await t.agentManifest(agent.manifestName, from))?.body?.runtime?.triggers ??
178
220
  {});
179
- const after = await triggersOf(t, agent);
221
+ const { triggers: after, declared } = await pinnedOf(t, agent);
180
222
  const limits = await readRuntimeLimits(t);
181
223
  const stamp = now.toISOString();
182
- const rows = new Map((await t.agentTriggers(agent.id)).map((row) => [row.triggerId, row]));
183
- for (const [triggerId, row] of rows)
184
- if (row.kind !== "wake" && after[triggerId] === undefined)
224
+ const all = await t.agentTriggers(agent.id);
225
+ const rows = new Map(all.filter((row) => originOf(row) === "manifest").map((row) => [row.triggerId, row]));
226
+ const taken = new Set(all.filter((row) => originOf(row) !== "manifest").map((row) => row.triggerId));
227
+ for (const [triggerId] of rows)
228
+ if (after[triggerId] === undefined)
185
229
  await t.deleteAgentTrigger(agent.id, triggerId);
230
+ if (declared)
231
+ for (const row of all)
232
+ if (originOf(row) === "agent" && row.status === "paused" && row.pausedReason === UNDECLARED) {
233
+ const next = row.definition ? nextFireAt(agent.id, row.triggerId, row.definition, now, limits) : null;
234
+ await t.putAgentTrigger({
235
+ ...row,
236
+ status: next === null ? "completed" : "active",
237
+ pausedReason: null,
238
+ nextFireAt: next?.toISOString() ?? null,
239
+ generation: row.generation + 1,
240
+ updatedAt: stamp,
241
+ });
242
+ }
186
243
  const timing = (trigger) => trigger === undefined ? undefined : JSON.stringify([trigger.schedule, trigger.timezone, trigger.at, trigger.webhook]);
187
244
  for (const [triggerId, trigger] of Object.entries(after)) {
245
+ if (taken.has(triggerId))
246
+ continue;
188
247
  const row = rows.get(triggerId);
189
248
  if (!row) {
190
249
  await t.putAgentTrigger({
@@ -201,6 +260,8 @@ export async function mergeAgentTriggers(t, agent, from, now = new Date()) {
201
260
  sessionId: null,
202
261
  note: null,
203
262
  effectId: null,
263
+ origin: "manifest",
264
+ ...NO_AGENT_FIELDS,
204
265
  createdAt: stamp,
205
266
  updatedAt: stamp,
206
267
  });
@@ -272,7 +333,9 @@ export async function fireTimer(ctx, key) {
272
333
  if (!target)
273
334
  return;
274
335
  const { agentId, triggerId } = target;
336
+ const runs = [];
275
337
  const armed = await ctx.store.tx(async (t) => {
338
+ runs.length = 0;
276
339
  const row = await t.agentTrigger(agentId, triggerId, { lock: true });
277
340
  if (!row || row.status !== "active" || row.nextFireAt === null)
278
341
  return undefined;
@@ -284,17 +347,31 @@ export async function fireTimer(ctx, key) {
284
347
  const agent = await liveEnvironment(t, agentId, { lock: true });
285
348
  if (!agent)
286
349
  return undefined;
287
- // A booked wake (D77) is its own trigger: its session, with its note as the message.
288
- const trigger = row.kind === "wake" ? wakeTriggerOf(row) : (await triggersOf(t, agent))[triggerId];
350
+ // A booked wake (D77) is its own trigger: its session, with its note as the message; an
351
+ // agent's (D83) is the row's own definition.
352
+ const pinned = await pinnedOf(t, agent);
353
+ const origin = originOf(row);
354
+ const trigger = definitionOf(row, pinned.triggers);
289
355
  if (!trigger) {
290
- await t.deleteAgentTrigger(agentId, triggerId);
356
+ // A manifest's that the version no longer has, or a wake with nothing to say. An agent's
357
+ // row always holds its definition.
358
+ if (origin !== "agent")
359
+ await t.deleteAgentTrigger(agentId, triggerId);
360
+ return undefined;
361
+ }
362
+ // An agent's trigger runs only while the version declares the capability: until one does, it
363
+ // waits paused and is not deleted.
364
+ if (origin === "agent" && !pinned.declared) {
365
+ const stamp = new Date(now).toISOString();
366
+ await t.putAgentTrigger({ ...row, status: "paused", pausedReason: UNDECLARED, nextFireAt: null, generation: row.generation + 1, updatedAt: stamp });
367
+ await t.agentEvent(agentId, "agent.trigger_paused", { triggerId, reason: UNDECLARED });
291
368
  return undefined;
292
369
  }
293
- await deliverRun(ctx, t, agent, triggerId, trigger, row, {
370
+ runs.push(await deliverRun(ctx, t, agent, triggerId, trigger, row, {
294
371
  runKey: `fire:${triggerId}:${due}`,
295
372
  dueAt: row.nextFireAt,
296
373
  late: now - due > hostTuning().triggers.lateGraceMs,
297
- });
374
+ }));
298
375
  const limits = await readRuntimeLimits(t);
299
376
  // A booked wake runs once and goes: the session may book the next.
300
377
  if (row.kind === "wake") {
@@ -314,9 +391,12 @@ export async function fireTimer(ctx, key) {
314
391
  await t.putAgentTrigger(updated);
315
392
  return updated;
316
393
  });
394
+ await appendRuns(ctx, runs);
317
395
  if (armed)
318
396
  await armTrigger(ctx, armed);
319
397
  }
398
+ /** Why an agent's trigger is paused: the version its Agent runs lacks `harness.engine.triggers` (D83). */
399
+ export const UNDECLARED = "undeclared";
320
400
  /** Who writes a triggered message: core, for the trigger (R4.9). */
321
401
  const triggerWriter = (triggerId) => ({
322
402
  source: { kind: "core", id: "runtime" },
@@ -330,6 +410,7 @@ export async function deliverRun(ctx, t, agent, triggerId, trigger, row, cause)
330
410
  const existing = await t.triggerRunByKey(agent.id, cause.runKey);
331
411
  if (existing)
332
412
  return existing;
413
+ const origin = row.kind === "wake" ? "wake" : (row.origin ?? "manifest");
333
414
  const firedAt = new Date().toISOString();
334
415
  const id = newRunId();
335
416
  const run = {
@@ -346,8 +427,11 @@ export async function deliverRun(ctx, t, agent, triggerId, trigger, row, cause)
346
427
  sessionId: null,
347
428
  turnId: null,
348
429
  queuedMessageId: null,
430
+ origin,
431
+ title: row.title ?? null,
432
+ streamedAt: null,
349
433
  };
350
- const outcome = await runOutcome(ctx, t, agent, triggerId, trigger, row.kind, id, firedAt, cause.payload);
434
+ const outcome = await runOutcome(ctx, t, agent, triggerId, trigger, row.kind, id, firedAt, cause.payload, row.createdSessionId ?? null);
351
435
  Object.assign(run, outcome);
352
436
  await t.insertTriggerRun(run);
353
437
  await t.agentEvent(agent.id, "agent.run", {
@@ -361,14 +445,17 @@ export async function deliverRun(ctx, t, agent, triggerId, trigger, row, cause)
361
445
  });
362
446
  return run;
363
447
  }
364
- async function runOutcome(ctx, t, agent, triggerId, trigger, kind, runId, firedAt, payload) {
448
+ async function runOutcome(ctx, t, agent, triggerId, trigger, kind, runId, firedAt, payload,
449
+ /** The session whose agent made the trigger (D83): a session it opens is that session's owner's. */
450
+ createdBy) {
365
451
  const skipped = (reason, sessionId = null) => ({ status: "skipped", reason, sessionId });
366
452
  const failed = (reason, sessionId = null) => ({ status: "failed", reason, sessionId });
367
453
  if (agent.desiredState === "off")
368
454
  return skipped("paused");
369
- const limits = await readRuntimeLimits(t);
455
+ // The hourly cap in force: the manifest's `runtime.limits.triggers.runsPerHour`, never above the Tenant's.
456
+ const { runsPerHour } = triggerLimitsInForce(await readRuntimeLimits(t), agent.applied?.limits);
370
457
  const hourAgo = new Date(Date.parse(firedAt) - HOUR_MS).toISOString();
371
- if ((await t.countTriggerRuns(agent.id, hourAgo, ["started", "queued"])) >= limits.triggerRunsPerHour)
458
+ if ((await t.countTriggerRuns(agent.id, hourAgo, ["started", "queued"])) >= runsPerHour)
372
459
  return skipped("rate_limited");
373
460
  // `new`: a session of its own, named for the run, so a repeated cause never opens a second.
374
461
  const sessionId = trigger.session === "new" ? `run-${runId}` : trigger.session;
@@ -377,10 +464,11 @@ async function runOutcome(ctx, t, agent, triggerId, trigger, kind, runId, firedA
377
464
  return failed("session_conflict", sessionId);
378
465
  try {
379
466
  if (!session) {
467
+ const maker = createdBy === null ? undefined : await t.get("sessions", createdBy);
380
468
  await openSessionIn(ctx, t, sessionId, {
381
469
  requestId: `trigger-${runId}`,
382
470
  agent: agent.id,
383
- ownerUserId: agent.ownerUserId ?? "installation",
471
+ ownerUserId: maker?.ownerUserId ?? agent.ownerUserId ?? "installation",
384
472
  });
385
473
  session = (await t.lockSession(sessionId));
386
474
  }
@@ -462,7 +550,32 @@ export function runView(row) {
462
550
  queuedMessageId: row.queuedMessageId,
463
551
  };
464
552
  }
465
- /** `GET /v1/agents/{id}/triggers`. */
553
+ /** One trigger as the API shows it: its definition and the Agent's state of it. */
554
+ export function triggerView(row, trigger, origin = "") {
555
+ const from = originOf(row);
556
+ return {
557
+ id: row.triggerId,
558
+ kind: row.kind,
559
+ origin: from,
560
+ title: row.title,
561
+ createdSessionId: row.createdSessionId,
562
+ status: row.status,
563
+ pausedReason: row.pausedReason,
564
+ ...(trigger.schedule === undefined ? {} : { schedule: trigger.schedule, timezone: trigger.timezone ?? "UTC" }),
565
+ ...(trigger.at === undefined ? {} : { at: trigger.at }),
566
+ session: trigger.session,
567
+ nextRunAt: row.status === "active" ? row.nextFireAt : null,
568
+ lastRunAt: row.lastFireAt,
569
+ ...(row.endpointId === null
570
+ ? {}
571
+ : { webhookUrl: `${origin}${webhookPath(row.endpointId)}`, secretSet: row.sealedSecret !== null }),
572
+ ...(from === "wake" ? {} : { prompt: trigger.prompt }),
573
+ };
574
+ }
575
+ /**
576
+ * `GET /v1/agents/{id}/triggers`: the manifest's triggers the Agent holds, and the triggers its
577
+ * agents made (D83); never a session's booked wake.
578
+ */
466
579
  export async function listTriggers(ctx, agentId, origin = "") {
467
580
  return ctx.store.tx(async (t) => {
468
581
  const agent = (await liveEnvironment(t, agentId)) ?? fail(404, `Agent ${agentId} not found`, { code: "not_found" });
@@ -470,34 +583,19 @@ export async function listTriggers(ctx, agentId, origin = "") {
470
583
  const rows = await t.agentTriggers(agentId);
471
584
  return {
472
585
  triggers: rows.flatMap((row) => {
473
- const trigger = triggers[row.triggerId];
474
- if (!trigger)
586
+ if (originOf(row) === "wake")
475
587
  return [];
476
- return [
477
- {
478
- id: row.triggerId,
479
- kind: row.kind,
480
- status: row.status,
481
- pausedReason: row.pausedReason,
482
- ...(trigger.schedule === undefined ? {} : { schedule: trigger.schedule, timezone: trigger.timezone ?? "UTC" }),
483
- ...(trigger.at === undefined ? {} : { at: trigger.at }),
484
- session: trigger.session,
485
- nextRunAt: row.status === "active" ? row.nextFireAt : null,
486
- lastRunAt: row.lastFireAt,
487
- ...(row.endpointId === null
488
- ? {}
489
- : { webhookUrl: `${origin}${webhookPath(row.endpointId)}`, secretSet: row.sealedSecret !== null }),
490
- },
491
- ];
588
+ const trigger = definitionOf(row, triggers);
589
+ return trigger ? [triggerView(row, trigger, origin)] : [];
492
590
  }),
493
591
  };
494
592
  });
495
593
  }
496
- /** The trigger `triggerId` of Agent `agentId`, locked with its Agent, or a 404. */
594
+ /** The trigger `triggerId` of Agent `agentId`, locked with its Agent, or a 404. A booked wake is not one. */
497
595
  async function lockedTrigger(t, agentId, triggerId) {
498
596
  const agent = (await liveEnvironment(t, agentId, { lock: true })) ?? fail(404, `Agent ${agentId} not found`, { code: "not_found" });
499
597
  const row = await t.agentTrigger(agentId, triggerId, { lock: true });
500
- const trigger = (await triggersOf(t, agent))[triggerId];
598
+ const trigger = row && originOf(row) !== "wake" ? definitionOf(row, await triggersOf(t, agent)) : undefined;
501
599
  if (!row || !trigger)
502
600
  return fail(404, `Agent ${agentId} has no trigger ${triggerId}`, { code: "not_found" });
503
601
  return { agent, row, trigger };
@@ -520,9 +618,7 @@ export async function setTriggerStatus(ctx, agentId, triggerId, action, origin =
520
618
  const next = nextFireAt(agentId, triggerId, trigger, now, await readRuntimeLimits(t));
521
619
  // An `at` runs once: one that ran, or whose time passed, does not run again.
522
620
  if (trigger.at !== undefined && (next === null || row.status === "completed"))
523
- fail(409, `Trigger ${triggerId} was due at ${trigger.at} and does not run again: save a manifest version with a new time.`, {
524
- code: "trigger_expired",
525
- });
621
+ fail(409, `Trigger ${triggerId} was due at ${trigger.at} and does not run again: ${originOf(row) === "agent" ? "the agent creates it again with a new time" : "save a manifest version with a new time"}.`, { code: "trigger_expired" });
526
622
  const updated = {
527
623
  ...row,
528
624
  status: "active",
@@ -539,20 +635,37 @@ export async function setTriggerStatus(ctx, agentId, triggerId, action, origin =
539
635
  await armTrigger(ctx, changed);
540
636
  return listTriggers(ctx, agentId, origin);
541
637
  }
638
+ /**
639
+ * `DELETE /v1/agents/{id}/triggers/{triggerId}` (D83): deletes a trigger one of the Agent's agents
640
+ * made (origin `agent`), with `agent.trigger_deleted`. A manifest's is the manifest's to drop
641
+ * (`409 trigger_from_manifest`); its runs stay.
642
+ */
643
+ export async function deleteTrigger(ctx, agentId, triggerId, principal) {
644
+ await ctx.store.tx(async (t) => {
645
+ const { row } = await lockedTrigger(t, agentId, triggerId);
646
+ if (originOf(row) !== "agent")
647
+ fail(409, `Trigger ${triggerId} is declared by the Agent's manifest: save a version without it, or pause it.`, {
648
+ code: "trigger_from_manifest",
649
+ });
650
+ await t.deleteAgentTrigger(agentId, triggerId);
651
+ await t.agentEvent(agentId, "agent.trigger_deleted", { triggerId, ...(principal === undefined ? {} : { principal }) });
652
+ });
653
+ }
542
654
  /**
543
655
  * `POST .../triggers/{triggerId}/run`: runs it now, whatever its status, keyed by the request's
544
656
  * `Idempotency-Key`, so a retried request makes one run.
545
657
  */
546
658
  export async function runTriggerNow(ctx, agentId, triggerId, idempotencyKey) {
547
- return ctx.store.tx(async (t) => {
659
+ const run = await ctx.store.tx(async (t) => {
548
660
  const { agent, row, trigger } = await lockedTrigger(t, agentId, triggerId);
549
- const run = await deliverRun(ctx, t, agent, triggerId, trigger, row, {
661
+ return deliverRun(ctx, t, agent, triggerId, trigger, row, {
550
662
  runKey: `manual:${triggerId}:${idempotencyKey}`,
551
663
  dueAt: new Date().toISOString(),
552
664
  late: false,
553
665
  });
554
- return runView(run);
555
666
  });
667
+ await appendRuns(ctx, [run]);
668
+ return runView(run);
556
669
  }
557
670
  /** `GET /v1/agents/{id}/runs`: newest first, `limit` per page, after cursor `before`. */
558
671
  export async function listRuns(ctx, agentId, options) {
@@ -615,7 +728,9 @@ export async function receiveWebhook(ctx, endpointId, delivery) {
615
728
  });
616
729
  if (!verified)
617
730
  return unsigned();
618
- return ctx.store.tx(async (t) => {
731
+ const runs = [];
732
+ const answer = await ctx.store.tx(async (t) => {
733
+ runs.length = 0;
619
734
  const { agent, row, trigger } = await lockedTrigger(t, found.agentId, found.triggerId);
620
735
  const runKey = `webhook:${row.triggerId}:${id}`;
621
736
  const repeated = await t.triggerRunByKey(agent.id, runKey);
@@ -630,8 +745,11 @@ export async function receiveWebhook(ctx, endpointId, delivery) {
630
745
  });
631
746
  if (!repeated)
632
747
  await t.putAgentTrigger({ ...row, lastFireAt: receivedAt, updatedAt: receivedAt });
748
+ runs.push(run);
633
749
  return { runId: run.id, status: run.status, reason: run.reason };
634
750
  });
751
+ await appendRuns(ctx, runs);
752
+ return answer;
635
753
  }
636
754
  // ---------------------------------------------------------------------------
637
755
  // Booked wakes (D77)
@@ -707,6 +825,8 @@ export async function serveRuntimeTool(t, s, request) {
707
825
  sessionId: s.id,
708
826
  note: input.note,
709
827
  effectId: request.effectId,
828
+ origin: "wake",
829
+ ...NO_AGENT_FIELDS,
710
830
  createdAt: existing?.createdAt ?? stamp,
711
831
  updatedAt: stamp,
712
832
  });
package/dist/version.d.ts CHANGED
@@ -1 +1 @@
1
- export declare const RUNTIME_VERSION = "0.40.0-beta";
1
+ export declare const RUNTIME_VERSION = "0.41.0-beta";
package/dist/version.js CHANGED
@@ -1,3 +1,3 @@
1
1
  // Kept in step with package.json by runtime/server/scripts/check-package.mjs, which runs in npm run check.
2
2
  // release:prepare also rewrites this when it bumps @nylorun/runtime.
3
- export const RUNTIME_VERSION = "0.40.0-beta";
3
+ export const RUNTIME_VERSION = "0.41.0-beta";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@nylorun/runtime",
3
- "version": "0.40.0-beta",
3
+ "version": "0.41.0-beta",
4
4
  "description": "Nylorun Runtime: the agent execution Host on Postgres, Restate and S2, and model providers.",
5
5
  "type": "module",
6
6
  "license": "Apache-2.0",
@@ -65,8 +65,8 @@
65
65
  "@hono/node-server": "^2.1.5",
66
66
  "@hono/zod-openapi": "^1.6.3",
67
67
  "@modelcontextprotocol/sdk": "^1.32.1",
68
- "@nylorun/core": "0.34.0-beta",
69
- "@nylorun/harness": "0.31.0-beta",
68
+ "@nylorun/core": "0.35.0-beta",
69
+ "@nylorun/harness": "0.32.0-beta",
70
70
  "@restatedev/restate-sdk": "1.17.3",
71
71
  "@restatedev/restate-sdk-clients": "1.17.3",
72
72
  "@s2-dev/streamstore": "0.28.0",