@nylorun/runtime 0.51.2-beta → 0.53.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 (53) hide show
  1. package/CHANGELOG.md +43 -0
  2. package/dist/adapters/sandbox/computer.d.ts +28 -0
  3. package/dist/adapters/sandbox/computer.js +184 -0
  4. package/dist/api/components.d.ts +48 -0
  5. package/dist/api/components.js +3 -1
  6. package/dist/api/http/routes/triggers.js +38 -6
  7. package/dist/api/openapi.js +1 -0
  8. package/dist/artifacts/disk.js +10 -2
  9. package/dist/harness/computer.d.ts +86 -0
  10. package/dist/harness/computer.js +273 -0
  11. package/dist/harness/executors.d.ts +5 -0
  12. package/dist/harness/executors.js +13 -5
  13. package/dist/harness/pod.d.ts +2 -0
  14. package/dist/harness/pod.js +1 -1
  15. package/dist/harness/service.js +58 -22
  16. package/dist/harness-api/server.js +17 -0
  17. package/dist/harness-api/start.js +2 -0
  18. package/dist/harness-api/workspace.d.ts +5 -0
  19. package/dist/harness-api/workspace.js +9 -5
  20. package/dist/host/harness-entry.js +7 -2
  21. package/dist/host/main.js +13 -0
  22. package/dist/host/stack-config.d.ts +13 -1
  23. package/dist/host/stack-config.js +42 -2
  24. package/dist/management-openapi.json +2 -1
  25. package/dist/openapi.json +348 -44
  26. package/dist/sandbox/computer.d.ts +53 -0
  27. package/dist/sandbox/computer.js +271 -0
  28. package/dist/sandbox/placement.d.ts +5 -2
  29. package/dist/sandbox/placement.js +6 -3
  30. package/dist/sandbox/pods/reconcile.js +1 -0
  31. package/dist/sandbox/pods/spec.d.ts +5 -0
  32. package/dist/sandbox/pods/spec.js +1 -1
  33. package/dist/sandbox/records.d.ts +2 -0
  34. package/dist/sandbox/records.js +11 -0
  35. package/dist/sandbox/types.d.ts +5 -2
  36. package/dist/store/postgres/drizzle/0049_agent_triggers_api.sql +3 -0
  37. package/dist/store/postgres/drizzle/meta/_journal.json +7 -0
  38. package/dist/store/postgres/schema.d.ts +21 -4
  39. package/dist/store/postgres/schema.js +10 -7
  40. package/dist/tenant/advance.js +6 -3
  41. package/dist/tenant/agent-triggers.js +5 -2
  42. package/dist/tenant/api-triggers.d.ts +33 -0
  43. package/dist/tenant/api-triggers.js +140 -0
  44. package/dist/tenant/context.d.ts +3 -0
  45. package/dist/tenant/runtime.js +34 -0
  46. package/dist/tenant/sessions.js +2 -1
  47. package/dist/tenant/sweep.js +2 -1
  48. package/dist/tenant/transcript.d.ts +8 -8
  49. package/dist/tenant/triggers.d.ts +6 -4
  50. package/dist/tenant/triggers.js +22 -11
  51. package/dist/version.d.ts +1 -1
  52. package/dist/version.js +1 -1
  53. package/package.json +3 -3
@@ -0,0 +1,140 @@
1
+ /**
2
+ * Triggers a client puts on one Agent (D88): `PUT /v1/agents/{agentId}/triggers/{triggerId}` with
3
+ * a `Trigger`, exactly as a definition's `runtime.triggers` entry holds it, and an optional title.
4
+ *
5
+ * - **Origin `api`.** The row keeps its own definition, like an agent-made trigger (D83), and the
6
+ * principal that put it. Upgrades and moves never touch it (`mergeAgentTriggers` merges
7
+ * `manifest` rows only); `DELETE` removes it; pause, resume, run and a webhook's secret work as
8
+ * for any trigger; it fires through `fireTimer` and `deliverRun` like every other.
9
+ * - **Create or replace.** A new id makes it active with its next time. The same id replaces it:
10
+ * `generation` goes up and the next time is worked out again, so the timer armed for the old
11
+ * time does nothing. A paused one stays paused; an `at` that ran is active again at its new time.
12
+ * - **The same body again changes nothing** and records no event, so a client may put its desired
13
+ * state as often as it likes.
14
+ * - **Conflicts.** An id the definition declares, or a row of another origin holds, is
15
+ * `409 trigger_conflict`: the API never replaces a trigger it didn't put.
16
+ * - **Checks and limits.** Those a definition save runs (`triggerIssues`), against the limits in
17
+ * force; `count` covers the Agent's `agent` and `api` triggers together (`422 trigger_limit`).
18
+ * - **Events.** `agent.trigger_created` or `agent.trigger_updated` (with `changed`) on the Agent.
19
+ *
20
+ * One transaction with the Agent locked; the timer is armed after the commit.
21
+ */
22
+ import { triggerKindOf } from "@nylorun/core/contracts";
23
+ import { liveEnvironment } from "./agents.js";
24
+ import { fail } from "./http.js";
25
+ import { triggerLimitsInForce } from "./limits.js";
26
+ import { readRuntimeLimits } from "./runtime-limits.js";
27
+ import { armTrigger, newEndpointId, nextFireAt, originOf, triggerIssues, triggersOf, triggerView } from "./triggers.js";
28
+ /** The definition fields a put compares, in a fixed order. */
29
+ const DEFINITION_FIELDS = ["schedule", "timezone", "at", "webhook", "session", "prompt"];
30
+ /** The `Trigger` part of a put body, with only the fields it has. */
31
+ export function definitionOfPut(body) {
32
+ const definition = {};
33
+ for (const field of DEFINITION_FIELDS)
34
+ if (body[field] !== undefined)
35
+ definition[field] = body[field];
36
+ return definition;
37
+ }
38
+ /** The definition fields that differ between `before` and `after`, and `title` when it changed. */
39
+ export function changedFields(before, after, titleBefore, titleAfter) {
40
+ const changed = DEFINITION_FIELDS.filter((field) => before[field] !== after[field]);
41
+ if (titleBefore !== titleAfter)
42
+ changed.push("title");
43
+ return changed;
44
+ }
45
+ /** The event payload's timing and session for `trigger`. */
46
+ function timingOf(trigger) {
47
+ return {
48
+ session: trigger.session,
49
+ ...(trigger.schedule === undefined ? {} : { schedule: trigger.schedule, timezone: trigger.timezone ?? "UTC" }),
50
+ ...(trigger.at === undefined ? {} : { at: trigger.at }),
51
+ };
52
+ }
53
+ /**
54
+ * `PUT /v1/agents/{agentId}/triggers/{triggerId}` (D88): creates or replaces the Agent's `api`
55
+ * trigger `triggerId` and answers it as the list shows it. `principal` is who put it; `origin` is
56
+ * the URL origin the request reached, for a webhook's URL.
57
+ */
58
+ export async function putTrigger(ctx, agentId, triggerId, body, principal, origin = "") {
59
+ const definition = definitionOfPut(body);
60
+ const title = body.title ?? null;
61
+ const { row, changed } = await ctx.store.tx(async (t) => {
62
+ const agent = (await liveEnvironment(t, agentId, { lock: true })) ?? fail(404, `Agent ${agentId} not found`, { code: "not_found" });
63
+ const existing = await t.agentTrigger(agentId, triggerId, { lock: true });
64
+ const declared = await triggersOf(t, agent);
65
+ const held = existing ? originOf(existing) : declared[triggerId] !== undefined ? "manifest" : undefined;
66
+ if (held !== undefined && held !== "api")
67
+ fail(409, held === "manifest"
68
+ ? `Trigger ${triggerId} comes from the Agent's definition: put your trigger under another id.`
69
+ : held === "agent"
70
+ ? `Trigger ${triggerId} was made by one of the Agent's agents: put your trigger under another id.`
71
+ : `${triggerId} is a session's booked wake: put your trigger under another id.`, { code: "trigger_conflict", details: { origin: held } });
72
+ const before = existing?.definition;
73
+ if (existing && before) {
74
+ const fields = changedFields(before, definition, existing.title, title);
75
+ // The same body again: nothing changes and nothing is recorded.
76
+ if (fields.length === 0)
77
+ return { row: existing, changed: undefined };
78
+ }
79
+ const now = new Date();
80
+ const stamp = now.toISOString();
81
+ const runtime = await readRuntimeLimits(t);
82
+ const inForce = triggerLimitsInForce(runtime, agent.applied?.limits);
83
+ if (!existing) {
84
+ // One count for every trigger not from the definition: the tool's and the API's.
85
+ const made = (await t.agentTriggers(agentId)).filter((other) => originOf(other) === "agent" || originOf(other) === "api").length;
86
+ if (made >= inForce.count)
87
+ fail(422, `Agent ${agentId} has ${made} triggers its agents or clients made, the most it can have (limits.triggers.count ${inForce.count}). Delete one to add another.`, {
88
+ code: "trigger_limit",
89
+ details: { limit: "triggers.count", max: inForce.count },
90
+ });
91
+ }
92
+ const issues = triggerIssues({ [triggerId]: definition }, { ...runtime, triggersPerAgent: inForce.count, triggerMinIntervalMs: inForce.minIntervalMs }, now, (id) => `trigger ${id}`);
93
+ if (issues.length > 0)
94
+ fail(400, issues.join(" "), { code: "invalid_request", details: { issues } });
95
+ const paused = existing?.status === "paused";
96
+ const next = paused ? null : nextFireAt(agentId, triggerId, definition, now, runtime);
97
+ const kind = triggerKindOf(definition);
98
+ const row = {
99
+ agentId,
100
+ triggerId,
101
+ kind,
102
+ // A webhook has no next time and stays active; an `at` with none has run.
103
+ status: paused ? "paused" : kind === "at" && next === null ? "completed" : "active",
104
+ pausedReason: paused ? existing.pausedReason : null,
105
+ generation: (existing?.generation ?? -1) + 1,
106
+ nextFireAt: next?.toISOString() ?? null,
107
+ lastFireAt: existing?.lastFireAt ?? null,
108
+ // A webhook keeps its endpoint and secret across replaces; another kind has none.
109
+ endpointId: kind === "webhook" ? (existing?.endpointId ?? newEndpointId()) : null,
110
+ sealedSecret: kind === "webhook" ? (existing?.sealedSecret ?? null) : null,
111
+ sessionId: null,
112
+ note: null,
113
+ effectId: null,
114
+ origin: "api",
115
+ principal: principal ?? existing?.principal ?? null,
116
+ title,
117
+ definition,
118
+ createdSessionId: null,
119
+ createdTurnId: null,
120
+ createdAt: existing?.createdAt ?? stamp,
121
+ updatedAt: stamp,
122
+ };
123
+ await t.putAgentTrigger(row);
124
+ const fields = existing && before ? changedFields(before, definition, existing.title, title) : undefined;
125
+ await t.agentEvent(agentId, existing ? "agent.trigger_updated" : "agent.trigger_created", {
126
+ triggerId,
127
+ kind,
128
+ origin: "api",
129
+ ...(row.principal === null ? {} : { principal: row.principal }),
130
+ ...timingOf(definition),
131
+ ...(title === null ? {} : { title }),
132
+ ...(fields === undefined ? {} : { changed: fields }),
133
+ nextRunAt: row.status === "active" ? row.nextFireAt : null,
134
+ });
135
+ return { row, changed: true };
136
+ });
137
+ if (changed)
138
+ await armTrigger(ctx, row);
139
+ return triggerView(row, definition, origin);
140
+ }
@@ -14,6 +14,7 @@
14
14
  * `wake` goes to `DurableExecution.wake`, which calls the Tenant's `advance` (`advance.ts`)
15
15
  * under ownership (§10.6); `abortLocal` aborts an advance running on this process.
16
16
  */
17
+ import type { TenantComputers } from "../sandbox/computer.js";
17
18
  import type { SessionHistory } from "./history.js";
18
19
  import type { IssuerScope, SessionModelChoice, SessionModelRecord, SubjectScope, TenantEnvelope } from "@nylorun/core/contracts";
19
20
  import type { DurableCheckpoint, FlowCheckpoint } from "@nylorun/harness/run";
@@ -390,6 +391,8 @@ export interface TenantPods {
390
391
  readonly client: SandboxesClient;
391
392
  /** The Runtime image the pods copy the engine from (`NYLORUN_SANDBOX_HARNESS_IMAGE`). */
392
393
  readonly harnessImage: string;
394
+ /** Computers (ADR-149): every pod Agent is one, reached through the sandbox gateway. */
395
+ readonly computer?: TenantComputers;
393
396
  }
394
397
  /** The harness a session runs (R4.1): the one it pinned, else ours. */
395
398
  export declare function harnessOf(session: Pick<Session, "harness">): string;
@@ -1,3 +1,20 @@
1
+ /**
2
+ * Composition root of an open Tenant: validates the config, opens the Session Store and the
3
+ * vault key, builds the services and the `TenantContext`, wires the seams, registers the
4
+ * Tenant's Worker handlers and arms its sweep, and implements `TenantHandle` (handle,
5
+ * summary, drain, close) by delegating to the Tenant modules.
6
+ *
7
+ * Seams wired here: `wireStreams()` connects the store's commits to Durable Streams (the
8
+ * relay, and the history and SSE readers); `wireControl()` follows the control bus on
9
+ * Postgres (cancels and resets from other processes); `wake` goes to the
10
+ * `DurableExecution`, whose handlers (`worker.ts`) call `advance` and `sweep`; `abortLocal`
11
+ * aborts an advance running on this process.
12
+ *
13
+ * Execution: the Host passes its `TenantExecution` (`TenantOpenHooks.execution`); without
14
+ * one, the Tenant runs its own in-process `MemoryExecution`. No lock file: several processes may open a Tenant, and ownership of each
15
+ * session (§10.6) keeps its advances apart.
16
+ */
17
+ import { computerManagers } from "../sandbox/computer.js";
1
18
  import { mkdirSync } from "node:fs";
2
19
  import { scriptedModel, toolFixtureModel, } from "../core/provider.js";
3
20
  import { tenantModelGate } from "../gates/in-process.js";
@@ -185,6 +202,23 @@ export class TenantRuntime {
185
202
  logger: config.logger,
186
203
  preference,
187
204
  isPod: async (sandboxId) => (await opened.tx((t) => t.environment(sandboxId)))?.kind === "pod",
205
+ ...(hooks.pods.computer
206
+ ? {
207
+ computer: (() => {
208
+ const managerOf = computerManagers(() => ctx, async (sessionId, turnId, type, payload) => {
209
+ if (ctx.closed)
210
+ return;
211
+ try {
212
+ await opened.tx((t) => t.event(sessionId, turnId, type, payload));
213
+ }
214
+ catch {
215
+ /* the session is gone (reset) or the store closed */
216
+ }
217
+ });
218
+ return (environmentId) => localWorkspace(managerOf(environmentId));
219
+ })(),
220
+ }
221
+ : {}),
188
222
  })
189
223
  : workspaces;
190
224
  // A model that is not the vault's (scripted, the fixture, an embedder's provider) is
@@ -93,7 +93,8 @@ export async function openSessionIn(ctx, t, id, body, access, options = {}) {
93
93
  await checkSkillLimit(t, pin.manifest, agent);
94
94
  const config = await readSandboxConfig(t);
95
95
  // Placement (D38, D52): where this session's harness may run, decided now and kept.
96
- checkPlacement(config, agent.kind, pin.harness);
96
+ // A computer's harness runs in the harness container (ADR-149).
97
+ checkPlacement(config, agent.kind, pin.harness, ctx.pods?.computer !== undefined);
97
98
  if (agent.kind === "pod")
98
99
  await requirePods(ctx.pods, `Agent ${agent.id}`);
99
100
  // The model the version names (`harness.model`, Host feature `agent-model`) is the new
@@ -328,7 +328,8 @@ async function reconcileStalePods(ctx, now) {
328
328
  pod.observed === "deleting" ||
329
329
  (pod.desired === "suspended" && pod.observed !== "suspended") ||
330
330
  // Running, but its engine is not connected: the pod may be gone, or its volume.
331
- (pod.desired === "running" && pod.observed === "running" && !ctx.harness.hosting(row.id)) ||
331
+ // (A computer has no engine connection, ADR-149.)
332
+ (pod.desired === "running" && pod.observed === "running" && !ctx.pods?.computer && !ctx.harness.hosting(row.id)) ||
332
333
  // Running past its idle limit: its idle timer is missing (or a turn holds it, which the
333
334
  // reconcile leaves alone).
334
335
  (pod.desired === "running" &&
@@ -231,7 +231,6 @@ export declare function toolCompleted(request: HostEffect, value: unknown, facts
231
231
  gate?: GateTimes | undefined;
232
232
  policy?: ToolPolicyFact | undefined;
233
233
  } | {
234
- redacted?: undefined;
235
234
  agent: import("@nylorun/core/define").AgentRef;
236
235
  error: {
237
236
  code: string;
@@ -241,6 +240,7 @@ export declare function toolCompleted(request: HostEffect, value: unknown, facts
241
240
  retryable?: boolean | undefined;
242
241
  };
243
242
  output?: undefined;
243
+ redacted?: undefined;
244
244
  toolName: string;
245
245
  status?: "cancelled" | "completed" | "failed" | undefined;
246
246
  kind?: "harness" | "http" | "mcp" | "runtime" | "sandbox" | undefined;
@@ -254,7 +254,6 @@ export declare function toolCompleted(request: HostEffect, value: unknown, facts
254
254
  gate?: GateTimes | undefined;
255
255
  policy?: ToolPolicyFact | undefined;
256
256
  } | {
257
- redacted?: undefined;
258
257
  agent?: undefined;
259
258
  error: {
260
259
  code: string;
@@ -264,6 +263,7 @@ export declare function toolCompleted(request: HostEffect, value: unknown, facts
264
263
  retryable?: boolean | undefined;
265
264
  };
266
265
  output?: undefined;
266
+ redacted?: undefined;
267
267
  toolName: string;
268
268
  status?: "cancelled" | "completed" | "failed" | undefined;
269
269
  kind?: "harness" | "http" | "mcp" | "runtime" | "sandbox" | undefined;
@@ -311,10 +311,10 @@ export declare function toolCompleted(request: HostEffect, value: unknown, facts
311
311
  gate?: GateTimes | undefined;
312
312
  policy?: ToolPolicyFact | undefined;
313
313
  } | {
314
- redacted?: undefined;
315
314
  agent: import("@nylorun/core/define").AgentRef;
316
315
  error?: undefined;
317
316
  output: unknown;
317
+ redacted?: undefined;
318
318
  toolName: string;
319
319
  status?: "cancelled" | "completed" | "failed" | undefined;
320
320
  kind?: "harness" | "http" | "mcp" | "runtime" | "sandbox" | undefined;
@@ -328,10 +328,10 @@ export declare function toolCompleted(request: HostEffect, value: unknown, facts
328
328
  gate?: GateTimes | undefined;
329
329
  policy?: ToolPolicyFact | undefined;
330
330
  } | {
331
- redacted?: undefined;
332
331
  agent?: undefined;
333
332
  error?: undefined;
334
333
  output: unknown;
334
+ redacted?: undefined;
335
335
  toolName: string;
336
336
  status?: "cancelled" | "completed" | "failed" | undefined;
337
337
  kind?: "harness" | "http" | "mcp" | "runtime" | "sandbox" | undefined;
@@ -395,7 +395,6 @@ export declare function toolCompleted(request: HostEffect, value: unknown, facts
395
395
  gate?: GateTimes | undefined;
396
396
  policy?: ToolPolicyFact | undefined;
397
397
  } | {
398
- redacted?: undefined;
399
398
  callId: string;
400
399
  invocationId: string;
401
400
  agent: import("@nylorun/core/define").AgentRef;
@@ -407,6 +406,7 @@ export declare function toolCompleted(request: HostEffect, value: unknown, facts
407
406
  retryable?: boolean | undefined;
408
407
  };
409
408
  output?: undefined;
409
+ redacted?: undefined;
410
410
  toolName: string;
411
411
  status?: "cancelled" | "completed" | "failed" | undefined;
412
412
  kind?: "harness" | "http" | "mcp" | "runtime" | "sandbox" | undefined;
@@ -420,7 +420,6 @@ export declare function toolCompleted(request: HostEffect, value: unknown, facts
420
420
  gate?: GateTimes | undefined;
421
421
  policy?: ToolPolicyFact | undefined;
422
422
  } | {
423
- redacted?: undefined;
424
423
  callId: string;
425
424
  invocationId: string;
426
425
  agent?: undefined;
@@ -432,6 +431,7 @@ export declare function toolCompleted(request: HostEffect, value: unknown, facts
432
431
  retryable?: boolean | undefined;
433
432
  };
434
433
  output?: undefined;
434
+ redacted?: undefined;
435
435
  toolName: string;
436
436
  status?: "cancelled" | "completed" | "failed" | undefined;
437
437
  kind?: "harness" | "http" | "mcp" | "runtime" | "sandbox" | undefined;
@@ -483,12 +483,12 @@ export declare function toolCompleted(request: HostEffect, value: unknown, facts
483
483
  gate?: GateTimes | undefined;
484
484
  policy?: ToolPolicyFact | undefined;
485
485
  } | {
486
- redacted?: undefined;
487
486
  callId: string;
488
487
  invocationId: string;
489
488
  agent: import("@nylorun/core/define").AgentRef;
490
489
  error?: undefined;
491
490
  output: unknown;
491
+ redacted?: undefined;
492
492
  toolName: string;
493
493
  status?: "cancelled" | "completed" | "failed" | undefined;
494
494
  kind?: "harness" | "http" | "mcp" | "runtime" | "sandbox" | undefined;
@@ -502,12 +502,12 @@ export declare function toolCompleted(request: HostEffect, value: unknown, facts
502
502
  gate?: GateTimes | undefined;
503
503
  policy?: ToolPolicyFact | undefined;
504
504
  } | {
505
- redacted?: undefined;
506
505
  callId: string;
507
506
  invocationId: string;
508
507
  agent?: undefined;
509
508
  error?: undefined;
510
509
  output: unknown;
510
+ redacted?: undefined;
511
511
  toolName: string;
512
512
  status?: "cancelled" | "completed" | "failed" | undefined;
513
513
  kind?: "harness" | "http" | "mcp" | "runtime" | "sandbox" | undefined;
@@ -36,6 +36,8 @@ export declare function nextFireAt(agentId: string, triggerId: string, trigger:
36
36
  export declare function triggerIssues(triggers: Readonly<Record<string, Trigger>> | undefined, limits: RuntimeLimits, now?: Date,
37
37
  /** How an issue names trigger `id`: its place in the manifest by default. */
38
38
  label?: (id: string) => string): string[];
39
+ /** The triggers of the manifest version an Agent runs. */
40
+ export declare function triggersOf(t: Tx, agent: Environment): Promise<Record<string, Trigger>>;
39
41
  /**
40
42
  * The line an agent-made trigger's run starts with under the `nylorun-triggers` plugin (ADR-106):
41
43
  * which trigger woke the turn, and that nobody is watching.
@@ -45,7 +47,7 @@ export declare function triggerFrame(triggerId: string, title: string | null | u
45
47
  export declare function originOf(row: Pick<AgentTriggerRow, "origin" | "kind">): AgentTriggerRow["origin"];
46
48
  /**
47
49
  * The definition trigger row `row` fires with (D83): a booked wake's, from its session and note;
48
- * an agent-made trigger's own, from the row; a manifest trigger's, the entry of the version the
50
+ * an agent-made or put trigger's own, from the row (D88); a manifest trigger's, the entry of the version the
49
51
  * Agent runs (`manifest`, read once by the caller). Undefined when it has none.
50
52
  */
51
53
  export declare function definitionOf(row: AgentTriggerRow, manifest: Readonly<Record<string, Trigger>>): Trigger | undefined;
@@ -113,9 +115,9 @@ export declare function setTriggerStatus(ctx: TenantContext, agentId: string, tr
113
115
  triggers: TriggerView[];
114
116
  }>;
115
117
  /**
116
- * `DELETE /v1/agents/{id}/triggers/{triggerId}` (D83): deletes a trigger one of the Agent's agents
117
- * made (origin `agent`), with `agent.trigger_deleted`. A manifest's is the manifest's to drop
118
- * (`409 trigger_from_manifest`); its runs stay.
118
+ * `DELETE /v1/agents/{id}/triggers/{triggerId}` (D83, D88): deletes a trigger one of the Agent's
119
+ * agents made (origin `agent`) or a client put (origin `api`), with `agent.trigger_deleted`. A
120
+ * manifest's is the manifest's to drop (`409 trigger_from_manifest`); its runs stay.
119
121
  */
120
122
  export declare function deleteTrigger(ctx: TenantContext, agentId: string, triggerId: string, principal?: string): Promise<void>;
121
123
  /**
@@ -164,7 +164,7 @@ label = (id) => `runtime.triggers.${id}`) {
164
164
  return issues;
165
165
  }
166
166
  /** The triggers of the manifest version an Agent runs. */
167
- async function triggersOf(t, agent) {
167
+ export async function triggersOf(t, agent) {
168
168
  return (await pinnedOf(t, agent)).triggers;
169
169
  }
170
170
  /** What the manifest version an Agent runs says of triggers: its own, and whether it declares the capability. */
@@ -192,7 +192,7 @@ export function originOf(row) {
192
192
  }
193
193
  /**
194
194
  * The definition trigger row `row` fires with (D83): a booked wake's, from its session and note;
195
- * an agent-made trigger's own, from the row; a manifest trigger's, the entry of the version the
195
+ * an agent-made or put trigger's own, from the row (D88); a manifest trigger's, the entry of the version the
196
196
  * Agent runs (`manifest`, read once by the caller). Undefined when it has none.
197
197
  */
198
198
  export function definitionOf(row, manifest) {
@@ -200,13 +200,14 @@ export function definitionOf(row, manifest) {
200
200
  case "wake":
201
201
  return wakeTriggerOf(row);
202
202
  case "agent":
203
+ case "api":
203
204
  return (row.definition ?? undefined);
204
205
  default:
205
206
  return manifest[row.triggerId];
206
207
  }
207
208
  }
208
209
  /** The columns a row of origin `manifest` or `wake` leaves empty. */
209
- const NO_AGENT_FIELDS = { title: null, definition: null, createdSessionId: null, createdTurnId: null };
210
+ const NO_AGENT_FIELDS = { title: null, definition: null, createdSessionId: null, createdTurnId: null, principal: null };
210
211
  /**
211
212
  * Writes the state of a new Agent's triggers (D74): every trigger of the version it is made from
212
213
  * starts active, with its next time. Arm them after the commit (`armAgentTriggers`).
@@ -386,8 +387,8 @@ export async function fireTimer(ctx, key) {
386
387
  const trigger = definitionOf(row, pinned.triggers);
387
388
  if (!trigger) {
388
389
  // A manifest's that the version no longer has, or a wake with nothing to say. An agent's
389
- // row always holds its definition.
390
- if (origin !== "agent")
390
+ // or a put row always holds its definition.
391
+ if (origin !== "agent" && origin !== "api")
391
392
  await t.deleteAgentTrigger(agentId, triggerId);
392
393
  return undefined;
393
394
  }
@@ -593,6 +594,7 @@ export function triggerView(row, trigger, origin = "") {
593
594
  origin: from,
594
595
  title: row.title,
595
596
  createdSessionId: row.createdSessionId,
597
+ principal: row.principal,
596
598
  status: row.status,
597
599
  pausedReason: row.pausedReason,
598
600
  ...(trigger.schedule === undefined ? {} : { schedule: trigger.schedule, timezone: trigger.timezone ?? "UTC" }),
@@ -652,7 +654,11 @@ export async function setTriggerStatus(ctx, agentId, triggerId, action, origin =
652
654
  const next = nextFireAt(agentId, triggerId, trigger, now, await readRuntimeLimits(t));
653
655
  // An `at` runs once: one that ran, or whose time passed, does not run again.
654
656
  if (trigger.at !== undefined && (next === null || row.status === "completed"))
655
- 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" });
657
+ fail(409, `Trigger ${triggerId} was due at ${trigger.at} and does not run again: ${originOf(row) === "agent"
658
+ ? "the agent creates it again with a new time"
659
+ : originOf(row) === "api"
660
+ ? "put it again with a new time"
661
+ : "save a manifest version with a new time"}.`, { code: "trigger_expired" });
656
662
  const updated = {
657
663
  ...row,
658
664
  status: "active",
@@ -670,19 +676,24 @@ export async function setTriggerStatus(ctx, agentId, triggerId, action, origin =
670
676
  return listTriggers(ctx, agentId, origin);
671
677
  }
672
678
  /**
673
- * `DELETE /v1/agents/{id}/triggers/{triggerId}` (D83): deletes a trigger one of the Agent's agents
674
- * made (origin `agent`), with `agent.trigger_deleted`. A manifest's is the manifest's to drop
675
- * (`409 trigger_from_manifest`); its runs stay.
679
+ * `DELETE /v1/agents/{id}/triggers/{triggerId}` (D83, D88): deletes a trigger one of the Agent's
680
+ * agents made (origin `agent`) or a client put (origin `api`), with `agent.trigger_deleted`. A
681
+ * manifest's is the manifest's to drop (`409 trigger_from_manifest`); its runs stay.
676
682
  */
677
683
  export async function deleteTrigger(ctx, agentId, triggerId, principal) {
678
684
  await ctx.store.tx(async (t) => {
679
685
  const { row } = await lockedTrigger(t, agentId, triggerId);
680
- if (originOf(row) !== "agent")
686
+ const origin = originOf(row);
687
+ if (origin !== "agent" && origin !== "api")
681
688
  fail(409, `Trigger ${triggerId} is declared by the Agent's manifest: save a version without it, or pause it.`, {
682
689
  code: "trigger_from_manifest",
683
690
  });
684
691
  await t.deleteAgentTrigger(agentId, triggerId);
685
- await t.agentEvent(agentId, "agent.trigger_deleted", { triggerId, ...(principal === undefined ? {} : { principal }) });
692
+ await t.agentEvent(agentId, "agent.trigger_deleted", {
693
+ triggerId,
694
+ origin: origin,
695
+ ...(principal === undefined ? {} : { principal }),
696
+ });
686
697
  });
687
698
  }
688
699
  /**
package/dist/version.d.ts CHANGED
@@ -1 +1 @@
1
- export declare const RUNTIME_VERSION = "0.51.2-beta";
1
+ export declare const RUNTIME_VERSION = "0.53.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.51.2-beta";
3
+ export const RUNTIME_VERSION = "0.53.0-beta";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@nylorun/runtime",
3
- "version": "0.51.2-beta",
3
+ "version": "0.53.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.8",
66
66
  "@hono/zod-openapi": "^1.6.3",
67
67
  "@modelcontextprotocol/sdk": "^1.32.2",
68
- "@nylorun/core": "0.44.0-beta",
69
- "@nylorun/harness": "0.36.4-beta",
68
+ "@nylorun/core": "0.46.0-beta",
69
+ "@nylorun/harness": "0.36.6-beta",
70
70
  "@restatedev/restate-sdk": "1.18.3",
71
71
  "@restatedev/restate-sdk-clients": "1.18.3",
72
72
  "@s2-dev/streamstore": "0.30.0",