@nylorun/runtime 0.39.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.
- package/CHANGELOG.md +21 -0
- package/dist/adapters/streams/s2.js +9 -2
- package/dist/api/a2a/endpoint.js +1 -1
- package/dist/api/a2a/protocol.d.ts +6 -0
- package/dist/api/a2a/protocol.js +13 -0
- package/dist/api/a2a/routes.js +1 -0
- package/dist/api/components.d.ts +131 -0
- package/dist/api/components.js +3 -1
- package/dist/api/http/app.js +2 -0
- package/dist/api/http/routes/runs.d.ts +8 -0
- package/dist/api/http/routes/runs.js +30 -0
- package/dist/api/http/routes/triggers.d.ts +2 -1
- package/dist/api/http/routes/triggers.js +32 -4
- package/dist/api/openapi.js +3 -1
- package/dist/gates/policy.d.ts +6 -4
- package/dist/gates/policy.js +10 -2
- package/dist/harness-api/record.js +51 -6
- package/dist/harness-api/start.js +15 -9
- package/dist/harness-api/tool-journal.js +3 -0
- package/dist/management-openapi.json +11 -6
- package/dist/openapi.json +507 -13
- package/dist/store/postgres/drizzle/0043_agent_triggers_origin.sql +11 -0
- package/dist/store/postgres/drizzle/0044_agent_triggers_origin_backfill.sql +8 -0
- package/dist/store/postgres/drizzle/meta/_journal.json +14 -0
- package/dist/store/postgres/schema.d.ts +151 -4
- package/dist/store/postgres/schema.js +25 -4
- package/dist/store/postgres/store.js +20 -2
- package/dist/store/types.d.ts +10 -0
- package/dist/streams/types.d.ts +16 -1
- package/dist/streams/types.js +8 -1
- package/dist/tenant/advance.js +7 -0
- package/dist/tenant/agent-triggers.d.ts +60 -0
- package/dist/tenant/agent-triggers.js +179 -0
- package/dist/tenant/agents.d.ts +1 -1
- package/dist/tenant/agents.js +5 -1
- package/dist/tenant/limits.d.ts +26 -7
- package/dist/tenant/limits.js +39 -6
- package/dist/tenant/runs-stream.d.ts +51 -0
- package/dist/tenant/runs-stream.js +164 -0
- package/dist/tenant/sweep.js +3 -0
- package/dist/tenant/triggers.d.ts +31 -5
- package/dist/tenant/triggers.js +168 -48
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/package.json +3 -3
package/dist/tenant/triggers.js
CHANGED
|
@@ -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 =
|
|
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
|
-
|
|
139
|
-
|
|
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.
|
|
174
|
-
*
|
|
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
|
|
221
|
+
const { triggers: after, declared } = await pinnedOf(t, agent);
|
|
180
222
|
const limits = await readRuntimeLimits(t);
|
|
181
223
|
const stamp = now.toISOString();
|
|
182
|
-
const
|
|
183
|
-
|
|
184
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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"])) >=
|
|
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
|
-
/**
|
|
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
|
-
|
|
474
|
-
if (!trigger)
|
|
586
|
+
if (originOf(row) === "wake")
|
|
475
587
|
return [];
|
|
476
|
-
|
|
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))
|
|
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
|
-
|
|
659
|
+
const run = await ctx.store.tx(async (t) => {
|
|
548
660
|
const { agent, row, trigger } = await lockedTrigger(t, agentId, triggerId);
|
|
549
|
-
|
|
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
|
-
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
69
|
-
"@nylorun/harness": "0.
|
|
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",
|