agents 0.22.0 → 0.24.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (215) hide show
  1. package/README.md +20 -1
  2. package/dist/{agent-routing-CnkaHb-v.d.ts → agent-routing-B2XLNMxq.d.ts} +1094 -443
  3. package/dist/agent-routing.d.ts +1 -1
  4. package/dist/agent-tool-types.d.ts +26 -26
  5. package/dist/{agent-tools-C0XJqWzB.d.ts → agent-tools-Dj5Mzlw7.d.ts} +7 -7
  6. package/dist/agent-tools.d.ts +21 -21
  7. package/dist/browser/ai.js +4 -1
  8. package/dist/browser/ai.js.map +1 -1
  9. package/dist/browser/tanstack-ai.js.map +1 -1
  10. package/dist/callable-decorator-DP__HhBA.d.ts +72 -0
  11. package/dist/callable-decorator.d.ts +18 -0
  12. package/dist/callable-decorator.js +71 -0
  13. package/dist/callable-decorator.js.map +1 -0
  14. package/dist/{capability-BjSKYpzg.js → capability-B4WbF81e.js} +2 -1
  15. package/dist/capability-B4WbF81e.js.map +1 -0
  16. package/dist/capability-runner-BUBa6Ake.d.ts +484 -0
  17. package/dist/channel-Bnm4S7T2.d.ts +491 -0
  18. package/dist/channels/ai-sdk.d.ts +46 -0
  19. package/dist/channels/ai-sdk.js +120 -0
  20. package/dist/channels/ai-sdk.js.map +1 -0
  21. package/dist/channels/email.d.ts +95 -0
  22. package/dist/channels/email.js +323 -0
  23. package/dist/channels/email.js.map +1 -0
  24. package/dist/channels/index.d.ts +233 -0
  25. package/dist/channels/index.js +608 -0
  26. package/dist/channels/index.js.map +1 -0
  27. package/dist/channels/slack.d.ts +140 -0
  28. package/dist/channels/slack.js +614 -0
  29. package/dist/channels/slack.js.map +1 -0
  30. package/dist/channels/tanstack-ai.d.ts +39 -0
  31. package/dist/channels/tanstack-ai.js +17 -0
  32. package/dist/channels/tanstack-ai.js.map +1 -0
  33. package/dist/channels/telegram.d.ts +106 -0
  34. package/dist/channels/telegram.js +427 -0
  35. package/dist/channels/telegram.js.map +1 -0
  36. package/dist/channels/voice.d.ts +45 -0
  37. package/dist/channels/voice.js +122 -0
  38. package/dist/channels/voice.js.map +1 -0
  39. package/dist/chat/index.d.ts +2319 -2010
  40. package/dist/chat/index.js +892 -518
  41. package/dist/chat/index.js.map +1 -1
  42. package/dist/chat-sdk/index.d.ts +7 -7
  43. package/dist/chat-sdk/index.js +1 -1
  44. package/dist/client-BxdkCuVy.d.ts +411 -0
  45. package/dist/client-C9DAjZIY.js +518 -0
  46. package/dist/client-C9DAjZIY.js.map +1 -0
  47. package/dist/{client-jagG8a9_.js → client-fyQrLGkD.js} +2 -2
  48. package/dist/{client-jagG8a9_.js.map → client-fyQrLGkD.js.map} +1 -1
  49. package/dist/client.d.ts +27 -283
  50. package/dist/client.js +2 -274
  51. package/dist/context/index.d.ts +216 -0
  52. package/dist/context/index.js +454 -0
  53. package/dist/context/index.js.map +1 -0
  54. package/dist/current-agent-C4w86kcN.d.ts +93 -0
  55. package/dist/current-agent-DhoDkSnH.js.map +1 -1
  56. package/dist/{diagnostics-CaBjfz4J.js → diagnostics-BzvaX2UT.js} +5 -1
  57. package/dist/diagnostics-BzvaX2UT.js.map +1 -0
  58. package/dist/diagnostics-C4jcz3VK.js +360 -0
  59. package/dist/diagnostics-C4jcz3VK.js.map +1 -0
  60. package/dist/durable-object-lifecycle-ei7M7lqB.d.ts +157 -0
  61. package/dist/index-BB0kqhIz.d.ts +101 -0
  62. package/dist/index-BVVgDSdq.d.ts +1 -0
  63. package/dist/index-Dlgtd3Mj.d.ts +78 -0
  64. package/dist/index-XDkuQ7zm.d.ts +89 -0
  65. package/dist/{index-DcSAZKsB.d.ts → index-YSKgfgg9.d.ts} +3 -1
  66. package/dist/index.d.ts +92 -83
  67. package/dist/index.js +4 -3
  68. package/dist/ingress-BfetZbMO.js +83 -0
  69. package/dist/ingress-BfetZbMO.js.map +1 -0
  70. package/dist/internal-CYlgHl1l.js +59 -0
  71. package/dist/internal-CYlgHl1l.js.map +1 -0
  72. package/dist/internal_context.d.ts +1 -1
  73. package/dist/lifecycle/index.d.ts +39 -19
  74. package/dist/lifecycle/index.js +2 -2
  75. package/dist/lifecycle-Mm_jQh7r.js +1314 -0
  76. package/dist/lifecycle-Mm_jQh7r.js.map +1 -0
  77. package/dist/mcp/client/index.d.ts +20 -20
  78. package/dist/mcp/client/index.js +1 -1
  79. package/dist/mcp/index.d.ts +35 -35
  80. package/dist/mcp/index.js +2 -2
  81. package/dist/observability/index.d.ts +1 -1
  82. package/dist/observability/index.js +1 -1
  83. package/dist/queue/index.d.ts +123 -0
  84. package/dist/queue/index.js +2 -0
  85. package/dist/queue-BC9F5cUo.js +406 -0
  86. package/dist/queue-BC9F5cUo.js.map +1 -0
  87. package/dist/react.d.ts +34 -16
  88. package/dist/react.js +31 -8
  89. package/dist/react.js.map +1 -1
  90. package/dist/{retries-CAvxtG9d.d.ts → retries-D9B2UCq3.d.ts} +23 -6
  91. package/dist/retries.d.ts +10 -6
  92. package/dist/retries.js +21 -1
  93. package/dist/retries.js.map +1 -1
  94. package/dist/routing/index.d.ts +137 -0
  95. package/dist/routing/index.js +244 -0
  96. package/dist/routing/index.js.map +1 -0
  97. package/dist/sanitize-D9TujEK8.js +79 -0
  98. package/dist/sanitize-D9TujEK8.js.map +1 -0
  99. package/dist/{scheduler-DQoTGoAW.d.ts → scheduler-BGq6M5Kd.d.ts} +20 -21
  100. package/dist/scheduler-D_KHqxBa.js +658 -0
  101. package/dist/scheduler-D_KHqxBa.js.map +1 -0
  102. package/dist/schedules/index.d.ts +1 -1
  103. package/dist/schedules/index.js +1 -1
  104. package/dist/sentence-chunker-BAidJ4DA.d.ts +68 -0
  105. package/dist/serializable.d.ts +1 -1
  106. package/dist/sessions/index.d.ts +441 -0
  107. package/dist/sessions/index.js +2063 -0
  108. package/dist/sessions/index.js.map +1 -0
  109. package/dist/skills/index.d.ts +99 -0
  110. package/dist/skills/index.js +254 -5
  111. package/dist/skills/index.js.map +1 -1
  112. package/dist/{src-5W6JNKVb.js → src-BNU3ZiJM.js} +1524 -1441
  113. package/dist/src-BNU3ZiJM.js.map +1 -0
  114. package/dist/state/index.d.ts +6 -0
  115. package/dist/state/index.js +137 -0
  116. package/dist/state/index.js.map +1 -0
  117. package/dist/streams/index.d.ts +120 -0
  118. package/dist/streams/index.js +107 -0
  119. package/dist/streams/index.js.map +1 -0
  120. package/dist/streams-BcE8JP2X.d.ts +370 -0
  121. package/dist/streams-CCPRV6dt.js +709 -0
  122. package/dist/streams-CCPRV6dt.js.map +1 -0
  123. package/dist/sub-routing.d.ts +12 -12
  124. package/dist/surface-bZZJqBka.js +17 -0
  125. package/dist/surface-bZZJqBka.js.map +1 -0
  126. package/dist/tasks/index.d.ts +64 -0
  127. package/dist/tasks/index.js +2 -0
  128. package/dist/tasks-D4nLqVSI.js +1656 -0
  129. package/dist/tasks-D4nLqVSI.js.map +1 -0
  130. package/dist/tasks-V2lCT8ZV.d.ts +517 -0
  131. package/dist/text-segment-joiner-BtAFQSA_.js +57 -0
  132. package/dist/text-segment-joiner-BtAFQSA_.js.map +1 -0
  133. package/dist/text-stream-CpdiKrJB.js +272 -0
  134. package/dist/text-stream-CpdiKrJB.js.map +1 -0
  135. package/dist/tokens-nHAKcN6M.js +52 -0
  136. package/dist/tokens-nHAKcN6M.js.map +1 -0
  137. package/dist/tool-schema-CBjGPrsQ.js +31 -0
  138. package/dist/tool-schema-CBjGPrsQ.js.map +1 -0
  139. package/dist/transport-protocol-BxyngQ_R.js +25 -0
  140. package/dist/transport-protocol-BxyngQ_R.js.map +1 -0
  141. package/dist/transport-protocol-ZTew5Wso.d.ts +27 -0
  142. package/dist/types-B7LojTe4.d.ts +202 -0
  143. package/dist/types-DCD6BNZH.d.ts +70 -0
  144. package/dist/types-_Faxb570.d.ts +439 -0
  145. package/dist/voice/client.d.ts +226 -0
  146. package/dist/voice/client.js +932 -0
  147. package/dist/voice/client.js.map +1 -0
  148. package/dist/voice/errors.d.ts +43 -0
  149. package/dist/voice/errors.js +41 -0
  150. package/dist/voice/errors.js.map +1 -0
  151. package/dist/voice/index.d.ts +271 -0
  152. package/dist/voice/index.js +1812 -0
  153. package/dist/voice/index.js.map +1 -0
  154. package/dist/voice/react.d.ts +167 -0
  155. package/dist/voice/react.js +234 -0
  156. package/dist/voice/react.js.map +1 -0
  157. package/dist/voice/sfu.d.ts +71 -0
  158. package/dist/voice/sfu.js +157 -0
  159. package/dist/voice/sfu.js.map +1 -0
  160. package/dist/voice/text.d.ts +6 -0
  161. package/dist/voice/text.js +2 -0
  162. package/dist/voice/types.d.ts +58 -0
  163. package/dist/voice/types.js +18 -0
  164. package/dist/voice/types.js.map +1 -0
  165. package/dist/voice/workers-ai.d.ts +136 -0
  166. package/dist/voice/workers-ai.js +568 -0
  167. package/dist/voice/workers-ai.js.map +1 -0
  168. package/dist/websockets/index.d.ts +301 -0
  169. package/dist/websockets/index.js +3 -0
  170. package/dist/websockets-D7IzWbZb.js +1005 -0
  171. package/dist/websockets-D7IzWbZb.js.map +1 -0
  172. package/dist/workflow-types.d.ts +25 -25
  173. package/dist/workflows.d.ts +21 -21
  174. package/dist/workflows.js +1 -1
  175. package/docs/agent-class.md +4 -4
  176. package/docs/agent-tools.md +2 -1
  177. package/docs/channels.md +323 -0
  178. package/docs/chat-agents.md +6 -13
  179. package/docs/client-sdk.md +30 -0
  180. package/docs/context.md +131 -0
  181. package/docs/index.md +16 -13
  182. package/docs/lifecycle.md +179 -57
  183. package/docs/long-running-agents.md +2 -2
  184. package/docs/mcp-servers.md +5 -1
  185. package/docs/queue.md +135 -241
  186. package/docs/resumable-streaming.md +1 -1
  187. package/docs/retries.md +1 -1
  188. package/docs/routing.md +105 -0
  189. package/docs/sessions.md +237 -871
  190. package/docs/streams.md +213 -0
  191. package/docs/sub-agents.md +184 -124
  192. package/docs/tasks.md +246 -0
  193. package/docs/voice.md +745 -0
  194. package/package.json +126 -14
  195. package/dist/capability-BjSKYpzg.js.map +0 -1
  196. package/dist/capability-runner-CvHGZqUu.d.ts +0 -150
  197. package/dist/client.js.map +0 -1
  198. package/dist/compaction-helpers-iiKMr2TQ.js +0 -340
  199. package/dist/compaction-helpers-iiKMr2TQ.js.map +0 -1
  200. package/dist/compaction-helpers-wUz6M3us.d.ts +0 -621
  201. package/dist/current-agent-CuMErtly.d.ts +0 -283
  202. package/dist/diagnostics-CaBjfz4J.js.map +0 -1
  203. package/dist/durable-object-lifecycle-D6nNQJJd.js +0 -862
  204. package/dist/durable-object-lifecycle-D6nNQJJd.js.map +0 -1
  205. package/dist/experimental/memory/session/index.d.ts +0 -671
  206. package/dist/experimental/memory/session/index.js +0 -2379
  207. package/dist/experimental/memory/session/index.js.map +0 -1
  208. package/dist/experimental/memory/utils/index.d.ts +0 -96
  209. package/dist/experimental/memory/utils/index.js +0 -79
  210. package/dist/experimental/memory/utils/index.js.map +0 -1
  211. package/dist/scheduler-CR9RHGos.js +0 -857
  212. package/dist/scheduler-CR9RHGos.js.map +0 -1
  213. package/dist/src-5W6JNKVb.js.map +0 -1
  214. package/dist/tool-output-truncation-CNnnGZQ3.js +0 -98
  215. package/dist/tool-output-truncation-CNnnGZQ3.js.map +0 -1
@@ -0,0 +1,244 @@
1
+ import { getAgentByName, routeAgentRequest } from "../agent-routing.js";
2
+ import { i as _classPrivateFieldInitSpec, n as _classPrivateFieldSet2, r as _assertClassBrand, t as _classPrivateFieldGet2 } from "../classPrivateFieldGet2-DZBYAB34.js";
3
+ import { t as LifecycleCapability } from "../capability-B4WbF81e.js";
4
+ import { t as _classPrivateMethodInitSpec } from "../classPrivateMethodInitSpec-qMjJ6sHQ.js";
5
+ //#region src/routing/routed-agents.ts
6
+ /**
7
+ * Catalog of the entries an owning Durable Object has created, keyed by
8
+ * route so several namespaces can share one owner. `WITHOUT ROWID` keeps
9
+ * an insert at one billed row; `agent_name` is a random UUID, so it needs
10
+ * no unique index of its own.
11
+ */
12
+ const TABLE = "cf_agents_routed_agents";
13
+ /**
14
+ * Derives the next per-route sequence number from a `MAX(seq)` read
15
+ * instead of a maintained counter row. Breaks ties between equal
16
+ * `Date.now()` values deterministically by write order, which a random
17
+ * entry `id` cannot: DO SQLite millisecond timestamps collide easily
18
+ * under rapid same-route writes.
19
+ *
20
+ * This scans every row for the route on each create()/setMetadata() —
21
+ * intentionally, not a missed index. DO SQLite bills roughly 1000
22
+ * writes for the cost of 1000 reads, so a maintained counter row (an
23
+ * extra write on every call) only wins at deep four-figure entries per
24
+ * route; a `(route, seq)` index would cost an extra write on every call
25
+ * too, since `seq` changes on every write it would index. `RoutedAgents`
26
+ * targets one owner's own catalog (chats, documents, sessions) — for a
27
+ * route expected to hold thousands of entries, benchmark before relying
28
+ * on this ordering; it is not built for that scale.
29
+ */
30
+ const NEXT_SEQ = `(SELECT COALESCE(MAX(seq), 0) + 1 FROM ${TABLE} WHERE route = ?)`;
31
+ function encodeMetadata(value) {
32
+ const encoded = JSON.stringify(value ?? null);
33
+ if (encoded === void 0) throw new TypeError("RoutedAgents metadata must be JSON-serializable");
34
+ return encoded;
35
+ }
36
+ var _namespace = /* @__PURE__ */ new WeakMap();
37
+ var _route = /* @__PURE__ */ new WeakMap();
38
+ var _RoutedAgents_brand = /* @__PURE__ */ new WeakSet();
39
+ /**
40
+ * A durable, routed collection of independent top-level Agents.
41
+ *
42
+ * Install this on the owning Durable Object, typically a per-user hub. It
43
+ * maps public entry IDs to opaque physical Agent names, handles catalog
44
+ * CRUD without waking any target, and forwards matching HTTP requests and
45
+ * WebSocket upgrades to the selected Agent. After an upgrade the target
46
+ * owns the socket, so ordinary frames never wake the owner. The target
47
+ * Agent needs no matching capability. Destroying the owner condemns every
48
+ * remaining entry with a few retries so targets don't casually outlive
49
+ * their catalog — this is best-effort, not a durability guarantee; see
50
+ * {@link RoutedAgents.dispose}.
51
+ *
52
+ * Pick a `route` that cannot appear as a literal path segment elsewhere
53
+ * under the owner (its own name, another route, or a path the owner's own
54
+ * `onRequest` handles) — forwarding matches every occurrence of the route
55
+ * segment in the path, so a coincidental match with no active entry
56
+ * behind it is answered `404` instead of reaching the owner.
57
+ *
58
+ * A forwarded suffix is not searched for a `/sub/{class}/{name}` dynamic
59
+ * agents marker: `Agent.fetch()` resolves that marker against the OWNER's
60
+ * exported classes before this capability's `onRequest` ever runs, so a
61
+ * matching marker is served as a facet of the owner, not forwarded to the
62
+ * target. Address a target's own dynamic agents through a direct
63
+ * connection to that target, not through the owner's route.
64
+ *
65
+ * @experimental The API surface may change before stabilizing.
66
+ */
67
+ var RoutedAgents = class extends LifecycleCapability {
68
+ /**
69
+ * @param options - Target binding and the route segment this capability
70
+ * claims. Install with `this.lifecycle.use()` before startup.
71
+ */
72
+ constructor(options) {
73
+ const route = options.route.replace(/^\/+|\/+$/g, "");
74
+ if (!/^[A-Za-z0-9_-]+$/.test(route)) throw new Error("RoutedAgents route must be one non-empty URL-safe path segment");
75
+ super(`routed-agents:${route}`);
76
+ _classPrivateMethodInitSpec(this, _RoutedAgents_brand);
77
+ _classPrivateFieldInitSpec(this, _namespace, void 0);
78
+ _classPrivateFieldInitSpec(this, _route, void 0);
79
+ _classPrivateFieldSet2(_namespace, this, options.namespace);
80
+ _classPrivateFieldSet2(_route, this, route);
81
+ }
82
+ /** Create an entry without waking the target Agent. */
83
+ async create(options) {
84
+ await this.lifecycle.ready();
85
+ const id = crypto.randomUUID();
86
+ const encoded = encodeMetadata(options?.metadata ?? null);
87
+ const now = Date.now();
88
+ _assertClassBrand(_RoutedAgents_brand, this, _sql).call(this, `INSERT INTO ${TABLE} (route, id, agent_name, status, metadata, created_at, updated_at, seq)
89
+ VALUES (?, ?, ?, 'active', ?, ?, ?, ${NEXT_SEQ})`, _classPrivateFieldGet2(_route, this), id, crypto.randomUUID(), encoded, now, now, _classPrivateFieldGet2(_route, this));
90
+ return {
91
+ id,
92
+ metadata: JSON.parse(encoded),
93
+ createdAt: now,
94
+ updatedAt: now
95
+ };
96
+ }
97
+ /** Resolve an active entry to an initialized, typed Agent stub. */
98
+ async get(id) {
99
+ await this.lifecycle.ready();
100
+ const agentName = _assertClassBrand(_RoutedAgents_brand, this, _agentName).call(this, id, "active");
101
+ return agentName ? _assertClassBrand(_RoutedAgents_brand, this, _stub).call(this, agentName) : null;
102
+ }
103
+ /**
104
+ * List active entries, most recently updated first. Entries whose
105
+ * `updatedAt` ties are ordered by actual write order, not by the
106
+ * random entry `id`.
107
+ */
108
+ async list() {
109
+ await this.lifecycle.ready();
110
+ return _assertClassBrand(_RoutedAgents_brand, this, _sql).call(this, `SELECT id, metadata, created_at AS createdAt, updated_at AS updatedAt
111
+ FROM ${TABLE} WHERE route = ? AND status = 'active'
112
+ ORDER BY updated_at DESC, seq DESC, id ASC`, _classPrivateFieldGet2(_route, this)).map((row) => ({
113
+ ...row,
114
+ metadata: JSON.parse(row.metadata)
115
+ }));
116
+ }
117
+ /** Replace an active entry's metadata. Returns false for unknown IDs. */
118
+ async setMetadata(id, metadata) {
119
+ await this.lifecycle.ready();
120
+ return _assertClassBrand(_RoutedAgents_brand, this, _sql).call(this, `UPDATE ${TABLE} SET metadata = ?, updated_at = ?, seq = ${NEXT_SEQ}
121
+ WHERE route = ? AND id = ? AND status = 'active' RETURNING id`, encodeMetadata(metadata), Date.now(), _classPrivateFieldGet2(_route, this), _classPrivateFieldGet2(_route, this), id).length > 0;
122
+ }
123
+ /**
124
+ * Make an entry unreachable, condemn its Agent, then remove the row.
125
+ * Returns false for unknown IDs.
126
+ *
127
+ * The target is condemned through Agent's deferred teardown, which
128
+ * durably marks it and returns without aborting the isolate; its storage
129
+ * is wiped on its own next wake, moments later, and the marker survives
130
+ * interruption. A failed RPC leaves a hidden `deleting` row so a
131
+ * repeated call retries.
132
+ */
133
+ async delete(id) {
134
+ await this.lifecycle.ready();
135
+ const agentName = _assertClassBrand(_RoutedAgents_brand, this, _agentName).call(this, id);
136
+ if (!agentName) return false;
137
+ _assertClassBrand(_RoutedAgents_brand, this, _sql).call(this, `UPDATE ${TABLE} SET status = 'deleting', updated_at = ?
138
+ WHERE route = ? AND id = ?`, Date.now(), _classPrivateFieldGet2(_route, this), id);
139
+ await _classPrivateFieldGet2(_namespace, this).get(_classPrivateFieldGet2(_namespace, this).idFromName(agentName))._cf_scheduleDestroy();
140
+ _assertClassBrand(_RoutedAgents_brand, this, _sql).call(this, `DELETE FROM ${TABLE} WHERE route = ? AND id = ?`, _classPrivateFieldGet2(_route, this), id);
141
+ return true;
142
+ }
143
+ onStart() {
144
+ _assertClassBrand(_RoutedAgents_brand, this, _sql).call(this, `CREATE TABLE IF NOT EXISTS ${TABLE} (
145
+ route TEXT NOT NULL,
146
+ id TEXT NOT NULL,
147
+ agent_name TEXT NOT NULL,
148
+ status TEXT NOT NULL CHECK (status IN ('active', 'deleting')),
149
+ metadata TEXT NOT NULL,
150
+ created_at INTEGER NOT NULL,
151
+ updated_at INTEGER NOT NULL,
152
+ seq INTEGER NOT NULL DEFAULT 0,
153
+ PRIMARY KEY (route, id)
154
+ ) WITHOUT ROWID`);
155
+ }
156
+ /**
157
+ * Condemn every remaining entry (including one already `deleting`, in
158
+ * case its own condemnation RPC never landed) when the owner itself is
159
+ * destroyed.
160
+ *
161
+ * `Agent.destroy()` disposes capabilities before it wipes its own
162
+ * storage, so the catalog is still readable here — without this, the
163
+ * catalog would vanish with the owner while every target it named kept
164
+ * running and billing storage, unreachable forever.
165
+ *
166
+ * This is best-effort, not a durability guarantee: `Agent.destroy()`
167
+ * wipes the owner's storage immediately after disposal regardless of
168
+ * whether any capability's `dispose()` reports failure, so a target
169
+ * that is still unreachable after retries here is orphaned for good —
170
+ * there is no later "repeated call retries" for a catalog row that no
171
+ * longer exists. Retrying briefly here converts the common transient
172
+ * failure into a condemned target instead of an orphan; it cannot
173
+ * convert a target that is durably unreachable.
174
+ */
175
+ async dispose() {
176
+ const entries = _assertClassBrand(_RoutedAgents_brand, this, _sql).call(this, `SELECT agent_name AS agentName FROM ${TABLE}
177
+ WHERE route = ? AND status IN ('active', 'deleting')`, _classPrivateFieldGet2(_route, this));
178
+ await Promise.all(entries.map(({ agentName }) => _assertClassBrand(_RoutedAgents_brand, this, _condemnWithRetry).call(this, agentName)));
179
+ }
180
+ /** Forward a matching HTTP request to the selected Agent. */
181
+ onRequest({ request }) {
182
+ return _assertClassBrand(_RoutedAgents_brand, this, _forward).call(this, request);
183
+ }
184
+ /** Forward a matching upgrade so the selected Agent owns the WebSocket. */
185
+ onWebSocketUpgrade({ request }) {
186
+ return _assertClassBrand(_RoutedAgents_brand, this, _forward).call(this, request);
187
+ }
188
+ };
189
+ async function _condemnWithRetry(agentName, attempts = 3) {
190
+ for (let attempt = 1; attempt <= attempts; attempt++) try {
191
+ await _classPrivateFieldGet2(_namespace, this).get(_classPrivateFieldGet2(_namespace, this).idFromName(agentName))._cf_scheduleDestroy();
192
+ return;
193
+ } catch (error) {
194
+ if (attempt === attempts) {
195
+ console.error(`RoutedAgents "${_classPrivateFieldGet2(_route, this)}" could not condemn ${agentName} on owner disposal after ${attempts} attempts; its storage will leak, since the owner's catalog — the only record of it — is wiped immediately after disposal`, error);
196
+ return;
197
+ }
198
+ await new Promise((resolve) => setTimeout(resolve, attempt * 50));
199
+ }
200
+ }
201
+ /**
202
+ * The route segment may also appear as the owner's own name or inside
203
+ * the forwarded suffix, so every `/{route}/{id}` occurrence is tried
204
+ * against the catalog and the first active entry wins. A route match
205
+ * with no active entry is a 404; no match at all lets the request
206
+ * continue to the owner's other capabilities.
207
+ */
208
+ async function _forward(request) {
209
+ const url = new URL(request.url);
210
+ const segments = url.pathname.split("/");
211
+ let matched = false;
212
+ for (let i = 1; i < segments.length - 1; i++) {
213
+ if (segments[i] !== _classPrivateFieldGet2(_route, this) || segments[i + 1] === "") continue;
214
+ matched = true;
215
+ const agentName = _assertClassBrand(_RoutedAgents_brand, this, _agentName).call(this, decode(segments[i + 1]), "active");
216
+ if (!agentName) continue;
217
+ url.pathname = `/${segments.slice(i + 2).join("/")}`;
218
+ return _classPrivateFieldGet2(_namespace, this).get(_classPrivateFieldGet2(_namespace, this).idFromName(agentName)).fetch(new Request(url, request));
219
+ }
220
+ return matched ? new Response("Agent not found", { status: 404 }) : void 0;
221
+ }
222
+ /** Initialized stub; the explicit generics keep inference shallow. */
223
+ function _stub(agentName) {
224
+ return getAgentByName(_classPrivateFieldGet2(_namespace, this), agentName);
225
+ }
226
+ function _agentName(id, status) {
227
+ const [row] = _assertClassBrand(_RoutedAgents_brand, this, _sql).call(this, `SELECT agent_name AS agentName FROM ${TABLE}
228
+ WHERE route = ? AND id = ? AND status = COALESCE(?, status)`, _classPrivateFieldGet2(_route, this), id, status ?? null);
229
+ return row?.agentName;
230
+ }
231
+ function _sql(query, ...values) {
232
+ return this.lifecycle.storage.sql.exec(query, ...values).toArray();
233
+ }
234
+ function decode(segment) {
235
+ try {
236
+ return decodeURIComponent(segment);
237
+ } catch {
238
+ return segment;
239
+ }
240
+ }
241
+ //#endregion
242
+ export { RoutedAgents, getAgentByName, routeAgentRequest };
243
+
244
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","names":[],"sources":["../../src/routing/routed-agents.ts"],"sourcesContent":["import { getAgentByName } from \"../agent-routing\";\nimport type { Agent } from \"../index\";\nimport { LifecycleCapability } from \"../lifecycle/capability\";\nimport type {\n CapabilityRequestContext,\n CapabilityWebSocketUpgradeContext\n} from \"../lifecycle/capability-runner\";\n\n/**\n * Catalog of the entries an owning Durable Object has created, keyed by\n * route so several namespaces can share one owner. `WITHOUT ROWID` keeps\n * an insert at one billed row; `agent_name` is a random UUID, so it needs\n * no unique index of its own.\n */\nconst TABLE = \"cf_agents_routed_agents\";\n\n/**\n * Derives the next per-route sequence number from a `MAX(seq)` read\n * instead of a maintained counter row. Breaks ties between equal\n * `Date.now()` values deterministically by write order, which a random\n * entry `id` cannot: DO SQLite millisecond timestamps collide easily\n * under rapid same-route writes.\n *\n * This scans every row for the route on each create()/setMetadata() —\n * intentionally, not a missed index. DO SQLite bills roughly 1000\n * writes for the cost of 1000 reads, so a maintained counter row (an\n * extra write on every call) only wins at deep four-figure entries per\n * route; a `(route, seq)` index would cost an extra write on every call\n * too, since `seq` changes on every write it would index. `RoutedAgents`\n * targets one owner's own catalog (chats, documents, sessions) — for a\n * route expected to hold thousands of entries, benchmark before relying\n * on this ordering; it is not built for that scale.\n */\nconst NEXT_SEQ = `(SELECT COALESCE(MAX(seq), 0) + 1 FROM ${TABLE} WHERE route = ?)`;\n\n/** A public entry in an {@link RoutedAgents}. */\nexport type RoutedAgentEntry<Metadata = unknown> = {\n /** Stable application-facing identifier used in routes. */\n readonly id: string;\n /** Application-owned metadata stored with the entry. */\n readonly metadata: Metadata | null;\n /** Creation time, as Unix milliseconds. */\n readonly createdAt: number;\n /** Time the entry or its metadata last changed, as Unix milliseconds. */\n readonly updatedAt: number;\n};\n\n/** Options for creating an entry in an {@link RoutedAgents}. */\nexport type RoutedAgentCreateOptions<Metadata = unknown> = {\n /** Initial application-owned metadata. */\n readonly metadata?: Metadata;\n};\n\n/** Configuration for an {@link RoutedAgents}. */\nexport type RoutedAgentsOptions<TAgent extends Agent> = {\n /** Top-level Durable Object namespace the entries are created in. */\n readonly namespace: DurableObjectNamespace<TAgent>;\n /** One URL-safe path segment under the owning Durable Object. */\n readonly route: string;\n};\n\ntype EntryRow = {\n id: string;\n metadata: string;\n createdAt: number;\n updatedAt: number;\n};\n\nfunction encodeMetadata(value: unknown): string {\n const encoded = JSON.stringify(value ?? null);\n if (encoded === undefined) {\n throw new TypeError(\"RoutedAgents metadata must be JSON-serializable\");\n }\n return encoded;\n}\n\n/**\n * A durable, routed collection of independent top-level Agents.\n *\n * Install this on the owning Durable Object, typically a per-user hub. It\n * maps public entry IDs to opaque physical Agent names, handles catalog\n * CRUD without waking any target, and forwards matching HTTP requests and\n * WebSocket upgrades to the selected Agent. After an upgrade the target\n * owns the socket, so ordinary frames never wake the owner. The target\n * Agent needs no matching capability. Destroying the owner condemns every\n * remaining entry with a few retries so targets don't casually outlive\n * their catalog — this is best-effort, not a durability guarantee; see\n * {@link RoutedAgents.dispose}.\n *\n * Pick a `route` that cannot appear as a literal path segment elsewhere\n * under the owner (its own name, another route, or a path the owner's own\n * `onRequest` handles) — forwarding matches every occurrence of the route\n * segment in the path, so a coincidental match with no active entry\n * behind it is answered `404` instead of reaching the owner.\n *\n * A forwarded suffix is not searched for a `/sub/{class}/{name}` dynamic\n * agents marker: `Agent.fetch()` resolves that marker against the OWNER's\n * exported classes before this capability's `onRequest` ever runs, so a\n * matching marker is served as a facet of the owner, not forwarded to the\n * target. Address a target's own dynamic agents through a direct\n * connection to that target, not through the owner's route.\n *\n * @experimental The API surface may change before stabilizing.\n */\nexport class RoutedAgents<\n TAgent extends Agent = Agent,\n Metadata = unknown\n> extends LifecycleCapability {\n readonly #namespace: DurableObjectNamespace<TAgent>;\n readonly #route: string;\n\n /**\n * @param options - Target binding and the route segment this capability\n * claims. Install with `this.lifecycle.use()` before startup.\n */\n constructor(options: RoutedAgentsOptions<TAgent>) {\n const route = options.route.replace(/^\\/+|\\/+$/g, \"\");\n if (!/^[A-Za-z0-9_-]+$/.test(route)) {\n throw new Error(\n \"RoutedAgents route must be one non-empty URL-safe path segment\"\n );\n }\n super(`routed-agents:${route}`);\n this.#namespace = options.namespace;\n this.#route = route;\n }\n\n /** Create an entry without waking the target Agent. */\n async create(\n options?: RoutedAgentCreateOptions<Metadata>\n ): Promise<RoutedAgentEntry<Metadata>> {\n await this.lifecycle.ready();\n const id = crypto.randomUUID();\n const encoded = encodeMetadata(options?.metadata ?? null);\n const now = Date.now();\n this.#sql(\n `INSERT INTO ${TABLE} (route, id, agent_name, status, metadata, created_at, updated_at, seq)\n VALUES (?, ?, ?, 'active', ?, ?, ?, ${NEXT_SEQ})`,\n this.#route,\n id,\n crypto.randomUUID(),\n encoded,\n now,\n now,\n this.#route\n );\n // Round-trip through JSON so this agrees with list()'s decoded copy —\n // returning the caller's object verbatim would diverge for values JSON\n // can't represent exactly (undefined fields, NaN, non-plain objects).\n return {\n id,\n metadata: JSON.parse(encoded) as Metadata | null,\n createdAt: now,\n updatedAt: now\n };\n }\n\n /** Resolve an active entry to an initialized, typed Agent stub. */\n async get(id: string): Promise<DurableObjectStub<TAgent> | null> {\n await this.lifecycle.ready();\n const agentName = this.#agentName(id, \"active\");\n return agentName ? this.#stub(agentName) : null;\n }\n\n /**\n * List active entries, most recently updated first. Entries whose\n * `updatedAt` ties are ordered by actual write order, not by the\n * random entry `id`.\n */\n async list(): Promise<ReadonlyArray<RoutedAgentEntry<Metadata>>> {\n await this.lifecycle.ready();\n return this.#sql<EntryRow>(\n `SELECT id, metadata, created_at AS createdAt, updated_at AS updatedAt\n FROM ${TABLE} WHERE route = ? AND status = 'active'\n ORDER BY updated_at DESC, seq DESC, id ASC`,\n this.#route\n ).map((row) => ({\n ...row,\n // SAFETY: written by create() or setMetadata() from a Metadata value.\n // Changing Metadata for an existing namespace needs an application\n // migration; JSON carries no type to recover.\n metadata: JSON.parse(row.metadata) as Metadata | null\n }));\n }\n\n /** Replace an active entry's metadata. Returns false for unknown IDs. */\n async setMetadata(id: string, metadata: Metadata | null): Promise<boolean> {\n await this.lifecycle.ready();\n return (\n this.#sql(\n `UPDATE ${TABLE} SET metadata = ?, updated_at = ?, seq = ${NEXT_SEQ}\n WHERE route = ? AND id = ? AND status = 'active' RETURNING id`,\n encodeMetadata(metadata),\n Date.now(),\n this.#route,\n this.#route,\n id\n ).length > 0\n );\n }\n\n /**\n * Make an entry unreachable, condemn its Agent, then remove the row.\n * Returns false for unknown IDs.\n *\n * The target is condemned through Agent's deferred teardown, which\n * durably marks it and returns without aborting the isolate; its storage\n * is wiped on its own next wake, moments later, and the marker survives\n * interruption. A failed RPC leaves a hidden `deleting` row so a\n * repeated call retries.\n */\n async delete(id: string): Promise<boolean> {\n await this.lifecycle.ready();\n const agentName = this.#agentName(id);\n if (!agentName) return false;\n this.#sql(\n `UPDATE ${TABLE} SET status = 'deleting', updated_at = ?\n WHERE route = ? AND id = ?`,\n Date.now(),\n this.#route,\n id\n );\n await this.#namespace\n .get(this.#namespace.idFromName(agentName))\n ._cf_scheduleDestroy();\n this.#sql(\n `DELETE FROM ${TABLE} WHERE route = ? AND id = ?`,\n this.#route,\n id\n );\n return true;\n }\n\n override onStart(): void {\n this.#sql(`CREATE TABLE IF NOT EXISTS ${TABLE} (\n route TEXT NOT NULL,\n id TEXT NOT NULL,\n agent_name TEXT NOT NULL,\n status TEXT NOT NULL CHECK (status IN ('active', 'deleting')),\n metadata TEXT NOT NULL,\n created_at INTEGER NOT NULL,\n updated_at INTEGER NOT NULL,\n seq INTEGER NOT NULL DEFAULT 0,\n PRIMARY KEY (route, id)\n ) WITHOUT ROWID`);\n }\n\n /**\n * Condemn every remaining entry (including one already `deleting`, in\n * case its own condemnation RPC never landed) when the owner itself is\n * destroyed.\n *\n * `Agent.destroy()` disposes capabilities before it wipes its own\n * storage, so the catalog is still readable here — without this, the\n * catalog would vanish with the owner while every target it named kept\n * running and billing storage, unreachable forever.\n *\n * This is best-effort, not a durability guarantee: `Agent.destroy()`\n * wipes the owner's storage immediately after disposal regardless of\n * whether any capability's `dispose()` reports failure, so a target\n * that is still unreachable after retries here is orphaned for good —\n * there is no later \"repeated call retries\" for a catalog row that no\n * longer exists. Retrying briefly here converts the common transient\n * failure into a condemned target instead of an orphan; it cannot\n * convert a target that is durably unreachable.\n */\n async dispose(): Promise<void> {\n const entries = this.#sql<{ agentName: string }>(\n `SELECT agent_name AS agentName FROM ${TABLE}\n WHERE route = ? AND status IN ('active', 'deleting')`,\n this.#route\n );\n await Promise.all(\n entries.map(({ agentName }) => this.#condemnWithRetry(agentName))\n );\n }\n\n async #condemnWithRetry(agentName: string, attempts = 3): Promise<void> {\n for (let attempt = 1; attempt <= attempts; attempt++) {\n try {\n await this.#namespace\n .get(this.#namespace.idFromName(agentName))\n ._cf_scheduleDestroy();\n return;\n } catch (error) {\n if (attempt === attempts) {\n console.error(\n `RoutedAgents \"${this.#route}\" could not condemn ${agentName} on owner disposal after ${attempts} attempts; its storage will leak, since the owner's catalog — the only record of it — is wiped immediately after disposal`,\n error\n );\n return;\n }\n await new Promise((resolve) => setTimeout(resolve, attempt * 50));\n }\n }\n }\n\n /** Forward a matching HTTP request to the selected Agent. */\n onRequest({\n request\n }: CapabilityRequestContext): Promise<Response | undefined> {\n return this.#forward(request);\n }\n\n /** Forward a matching upgrade so the selected Agent owns the WebSocket. */\n onWebSocketUpgrade({\n request\n }: CapabilityWebSocketUpgradeContext): Promise<Response | undefined> {\n return this.#forward(request);\n }\n\n /**\n * The route segment may also appear as the owner's own name or inside\n * the forwarded suffix, so every `/{route}/{id}` occurrence is tried\n * against the catalog and the first active entry wins. A route match\n * with no active entry is a 404; no match at all lets the request\n * continue to the owner's other capabilities.\n */\n async #forward(request: Request): Promise<Response | undefined> {\n const url = new URL(request.url);\n const segments = url.pathname.split(\"/\");\n let matched = false;\n for (let i = 1; i < segments.length - 1; i++) {\n if (segments[i] !== this.#route || segments[i + 1] === \"\") continue;\n matched = true;\n const agentName = this.#agentName(decode(segments[i + 1]), \"active\");\n if (!agentName) continue;\n url.pathname = `/${segments.slice(i + 2).join(\"/\")}`;\n return this.#namespace\n .get(this.#namespace.idFromName(agentName))\n .fetch(new Request(url, request));\n }\n return matched\n ? new Response(\"Agent not found\", { status: 404 })\n : undefined;\n }\n\n /** Initialized stub; the explicit generics keep inference shallow. */\n #stub(agentName: string): Promise<DurableObjectStub<TAgent>> {\n return getAgentByName<Cloudflare.Env, TAgent>(this.#namespace, agentName);\n }\n\n #agentName(id: string, status?: \"active\"): string | undefined {\n const [row] = this.#sql<{ agentName: string }>(\n `SELECT agent_name AS agentName FROM ${TABLE}\n WHERE route = ? AND id = ? AND status = COALESCE(?, status)`,\n this.#route,\n id,\n status ?? null\n );\n return row?.agentName;\n }\n\n #sql<Row extends Record<string, SqlStorageValue>>(\n query: string,\n ...values: SqlStorageValue[]\n ): Row[] {\n return this.lifecycle.storage.sql.exec<Row>(query, ...values).toArray();\n }\n}\n\nfunction decode(segment: string): string {\n try {\n return decodeURIComponent(segment);\n } catch {\n return segment;\n }\n}\n"],"mappings":"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"}
@@ -0,0 +1,79 @@
1
+ //#region src/sessions/sanitize.ts
2
+ /**
3
+ * Message hygiene shared by every Sessions write: UTF-8 measurement without a
4
+ * full encoded copy, and removal of ephemeral provider metadata that must not
5
+ * be stored or replayed.
6
+ */
7
+ const textEncoder = new TextEncoder();
8
+ const BYTE_LENGTH_BUFFER_BYTES = 16 * 1024;
9
+ const BYTE_LENGTH_WINDOW_CHARS = 16 * 1024;
10
+ /**
11
+ * Measure UTF-8 byte length without allocating a complete encoded copy.
12
+ * Memory stays bounded by a 16 KiB buffer even for near-row-limit strings.
13
+ */
14
+ function byteLength(s) {
15
+ const buffer = new Uint8Array(BYTE_LENGTH_BUFFER_BYTES);
16
+ let offset = 0;
17
+ let bytes = 0;
18
+ while (offset < s.length) {
19
+ let end = Math.min(s.length, offset + BYTE_LENGTH_WINDOW_CHARS);
20
+ if (end < s.length && end > offset && isHighSurrogate(s.charCodeAt(end - 1))) end--;
21
+ const { read, written } = textEncoder.encodeInto(s.slice(offset, end), buffer);
22
+ if (read === 0) break;
23
+ offset += read;
24
+ bytes += written;
25
+ }
26
+ return bytes;
27
+ }
28
+ function isHighSurrogate(code) {
29
+ return code >= 55296 && code <= 56319;
30
+ }
31
+ /**
32
+ * Sanitize a message for persistence by removing ephemeral provider-specific
33
+ * data that should not be stored or sent back in subsequent requests.
34
+ *
35
+ * 1. Strips OpenAI ephemeral fields (itemId, reasoningEncryptedContent)
36
+ * 2. Filters truly empty reasoning parts (no text, no remaining providerMetadata)
37
+ */
38
+ function sanitizeMessage(message) {
39
+ const parts = message.parts.flatMap((part) => {
40
+ let sanitized = part;
41
+ for (const key of ["providerMetadata", "callProviderMetadata"]) {
42
+ const metadata = sanitized[key];
43
+ if (metadata && typeof metadata === "object" && "openai" in metadata) sanitized = stripOpenAIMetadata(sanitized, key);
44
+ }
45
+ if (sanitized.type === "reasoning") {
46
+ const text = sanitized.text;
47
+ if (typeof text !== "string" || text.trim() === "") {
48
+ const metadata = sanitized.providerMetadata;
49
+ if (!(metadata && typeof metadata === "object" && Object.keys(metadata).length > 0)) return [];
50
+ }
51
+ }
52
+ return [sanitized];
53
+ });
54
+ return {
55
+ ...message,
56
+ parts
57
+ };
58
+ }
59
+ function stripOpenAIMetadata(part, metadataKey) {
60
+ const metadata = part[metadataKey];
61
+ if (!metadata?.openai) return part;
62
+ const { itemId: _itemId, reasoningEncryptedContent: _rec, ...restOpenai } = metadata.openai;
63
+ const { openai: _openai, ...restMetadata } = metadata;
64
+ let next;
65
+ if (Object.keys(restOpenai).length > 0) next = {
66
+ ...restMetadata,
67
+ openai: restOpenai
68
+ };
69
+ else if (Object.keys(restMetadata).length > 0) next = restMetadata;
70
+ const { [metadataKey]: _old, ...rest } = part;
71
+ return next ? {
72
+ ...rest,
73
+ [metadataKey]: next
74
+ } : rest;
75
+ }
76
+ //#endregion
77
+ export { sanitizeMessage as n, byteLength as t };
78
+
79
+ //# sourceMappingURL=sanitize-D9TujEK8.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"sanitize-D9TujEK8.js","names":[],"sources":["../src/sessions/sanitize.ts"],"sourcesContent":["/**\n * Message hygiene shared by every Sessions write: UTF-8 measurement without a\n * full encoded copy, and removal of ephemeral provider metadata that must not\n * be stored or replayed.\n */\n\nconst textEncoder = new TextEncoder();\nconst BYTE_LENGTH_BUFFER_BYTES = 16 * 1024;\nconst BYTE_LENGTH_WINDOW_CHARS = 16 * 1024;\n\n/**\n * Measure UTF-8 byte length without allocating a complete encoded copy.\n * Memory stays bounded by a 16 KiB buffer even for near-row-limit strings.\n */\nexport function byteLength(s: string): number {\n const buffer = new Uint8Array(BYTE_LENGTH_BUFFER_BYTES);\n let offset = 0;\n let bytes = 0;\n while (offset < s.length) {\n let end = Math.min(s.length, offset + BYTE_LENGTH_WINDOW_CHARS);\n if (\n end < s.length &&\n end > offset &&\n isHighSurrogate(s.charCodeAt(end - 1))\n ) {\n end--;\n }\n const { read, written } = textEncoder.encodeInto(\n s.slice(offset, end),\n buffer\n );\n if (read === 0) break;\n offset += read;\n bytes += written;\n }\n return bytes;\n}\n\nfunction isHighSurrogate(code: number): boolean {\n return code >= 0xd800 && code <= 0xdbff;\n}\n\ntype PartRecord = Record<string, unknown> & { type: string };\n\n/**\n * Sanitize a message for persistence by removing ephemeral provider-specific\n * data that should not be stored or sent back in subsequent requests.\n *\n * 1. Strips OpenAI ephemeral fields (itemId, reasoningEncryptedContent)\n * 2. Filters truly empty reasoning parts (no text, no remaining providerMetadata)\n */\nexport function sanitizeMessage<M extends { parts: readonly object[] }>(\n message: M\n): M {\n const parts = (message.parts as readonly PartRecord[]).flatMap((part) => {\n let sanitized = part;\n for (const key of [\"providerMetadata\", \"callProviderMetadata\"] as const) {\n const metadata = sanitized[key];\n if (\n metadata &&\n typeof metadata === \"object\" &&\n \"openai\" in (metadata as Record<string, unknown>)\n ) {\n sanitized = stripOpenAIMetadata(sanitized, key);\n }\n }\n if (sanitized.type === \"reasoning\") {\n const text = sanitized.text;\n if (typeof text !== \"string\" || text.trim() === \"\") {\n const metadata = sanitized.providerMetadata;\n const keep =\n metadata &&\n typeof metadata === \"object\" &&\n Object.keys(metadata as object).length > 0;\n if (!keep) return [];\n }\n }\n return [sanitized];\n });\n return { ...message, parts };\n}\n\nfunction stripOpenAIMetadata(\n part: PartRecord,\n metadataKey: \"providerMetadata\" | \"callProviderMetadata\"\n): PartRecord {\n const metadata = part[metadataKey] as {\n openai?: Record<string, unknown>;\n [key: string]: unknown;\n };\n if (!metadata?.openai) return part;\n\n const {\n itemId: _itemId,\n reasoningEncryptedContent: _rec,\n ...restOpenai\n } = metadata.openai;\n const { openai: _openai, ...restMetadata } = metadata;\n\n let next: Record<string, unknown> | undefined;\n if (Object.keys(restOpenai).length > 0) {\n next = { ...restMetadata, openai: restOpenai };\n } else if (Object.keys(restMetadata).length > 0) {\n next = restMetadata;\n }\n\n const { [metadataKey]: _old, ...rest } = part;\n return next\n ? ({ ...rest, [metadataKey]: next } as PartRecord)\n : (rest as PartRecord);\n}\n"],"mappings":";;;;;;AAMA,MAAM,cAAc,IAAI,YAAY;AACpC,MAAM,2BAA2B,KAAK;AACtC,MAAM,2BAA2B,KAAK;;;;;AAMtC,SAAgB,WAAW,GAAmB;CAC5C,MAAM,SAAS,IAAI,WAAW,wBAAwB;CACtD,IAAI,SAAS;CACb,IAAI,QAAQ;CACZ,OAAO,SAAS,EAAE,QAAQ;EACxB,IAAI,MAAM,KAAK,IAAI,EAAE,QAAQ,SAAS,wBAAwB;EAC9D,IACE,MAAM,EAAE,UACR,MAAM,UACN,gBAAgB,EAAE,WAAW,MAAM,CAAC,CAAC,GAErC;EAEF,MAAM,EAAE,MAAM,YAAY,YAAY,WACpC,EAAE,MAAM,QAAQ,GAAG,GACnB,MACF;EACA,IAAI,SAAS,GAAG;EAChB,UAAU;EACV,SAAS;CACX;CACA,OAAO;AACT;AAEA,SAAS,gBAAgB,MAAuB;CAC9C,OAAO,QAAQ,SAAU,QAAQ;AACnC;;;;;;;;AAWA,SAAgB,gBACd,SACG;CACH,MAAM,QAAS,QAAQ,MAAgC,SAAS,SAAS;EACvE,IAAI,YAAY;EAChB,KAAK,MAAM,OAAO,CAAC,oBAAoB,sBAAsB,GAAY;GACvE,MAAM,WAAW,UAAU;GAC3B,IACE,YACA,OAAO,aAAa,YACpB,YAAa,UAEb,YAAY,oBAAoB,WAAW,GAAG;EAElD;EACA,IAAI,UAAU,SAAS,aAAa;GAClC,MAAM,OAAO,UAAU;GACvB,IAAI,OAAO,SAAS,YAAY,KAAK,KAAK,MAAM,IAAI;IAClD,MAAM,WAAW,UAAU;IAK3B,IAAI,EAHF,YACA,OAAO,aAAa,YACpB,OAAO,KAAK,QAAkB,CAAC,CAAC,SAAS,IAChC,OAAO,CAAC;GACrB;EACF;EACA,OAAO,CAAC,SAAS;CACnB,CAAC;CACD,OAAO;EAAE,GAAG;EAAS;CAAM;AAC7B;AAEA,SAAS,oBACP,MACA,aACY;CACZ,MAAM,WAAW,KAAK;CAItB,IAAI,CAAC,UAAU,QAAQ,OAAO;CAE9B,MAAM,EACJ,QAAQ,SACR,2BAA2B,MAC3B,GAAG,eACD,SAAS;CACb,MAAM,EAAE,QAAQ,SAAS,GAAG,iBAAiB;CAE7C,IAAI;CACJ,IAAI,OAAO,KAAK,UAAU,CAAC,CAAC,SAAS,GACnC,OAAO;EAAE,GAAG;EAAc,QAAQ;CAAW;MACxC,IAAI,OAAO,KAAK,YAAY,CAAC,CAAC,SAAS,GAC5C,OAAO;CAGT,MAAM,GAAG,cAAc,MAAM,GAAG,SAAS;CACzC,OAAO,OACF;EAAE,GAAG;GAAO,cAAc;CAAK,IAC/B;AACP"}
@@ -1,8 +1,10 @@
1
1
  import {
2
+ _ as LifecycleJobContext,
3
+ d as LifecycleRouteContext,
2
4
  s as LifecycleCapability,
3
- u as LifecycleRouteContext
4
- } from "./capability-runner-CvHGZqUu.js";
5
- import { t as RetryOptions } from "./retries-CAvxtG9d.js";
5
+ v as LifecycleJobOutcome
6
+ } from "./capability-runner-BUBa6Ake.js";
7
+ import { t as RetryOptions } from "./retries-D9B2UCq3.js";
6
8
 
7
9
  //#region src/schedules/types.d.ts
8
10
  /**
@@ -128,10 +130,10 @@ interface SchedulerOptions<
128
130
  * Persistent task scheduling for a Lifecycle Object.
129
131
  *
130
132
  * Register callbacks in the constructor and install the instance with
131
- * `Lifecycle.use()`. Scheduler owns its SQL schema, task CRUD, callback
132
- * retries, and due-row processing. It contributes its next wake time while
133
- * Lifecycle owns the physical alarm, and it runs registered callbacks
134
- * through Lifecycle's host invocation boundary.
133
+ * `Lifecycle.use()`. Scheduler validates schedules and pushes them into the
134
+ * Lifecycle job queue; Lifecycle owns the physical alarm and the alarm
135
+ * event loop, and Scheduler runs registered callbacks through Lifecycle's
136
+ * host invocation boundary when its jobs come due.
135
137
  *
136
138
  * @experimental The API surface may change before stabilizing.
137
139
  */
@@ -151,14 +153,17 @@ declare class Scheduler<
151
153
  * name-based scheduling API.
152
154
  */
153
155
  constructor(options?: SchedulerOptions<Handlers>);
154
- /** Initialize and migrate schedule storage during Lifecycle startup. */
156
+ /** Migrate legacy schedule storage into the Lifecycle job queue. */
155
157
  onStart(): Promise<void>;
156
- /** Execute due schedule rows during the Lifecycle alarm phase. */
157
- onAlarm(): Promise<void>;
158
+ /** Drive one due schedule job dispatched by the Lifecycle event loop. */
159
+ onJob(context: LifecycleJobContext): Promise<LifecycleJobOutcome | void>;
160
+ /** Observe one schedule's terminal application failure. */
161
+ onJobError(
162
+ context: LifecycleJobContext,
163
+ error: unknown
164
+ ): Promise<LifecycleJobOutcome | void>;
158
165
  /** Handle Scheduler protocol messages routed by another Lifecycle. */
159
166
  onRoute(context: LifecycleRouteContext): Promise<unknown>;
160
- /** Contribute the earliest pending schedule to Lifecycle alarm selection. */
161
- getNextAlarm(): number | null;
162
167
  /** Set a delayed, dated, or cron schedule for a registered callback. */
163
168
  set<Name extends keyof Handlers & string>(
164
169
  when: Date | string | number,
@@ -200,14 +205,8 @@ declare class Scheduler<
200
205
  __DO_NOT_USE_WILL_REMOVE__getSchedules<T = string>(
201
206
  criteria?: ScheduleCriteria
202
207
  ): Schedule<T>[];
203
- /** @internal Remove rows owned by one routed Lifecycle subtree. */
204
- __DO_NOT_USE_WILL_BREAK__cleanupRoutePrefix(prefix: string): void;
205
- /** @internal Apply Agent's outer alarm memory-limit policy to Scheduler rows. */
206
- __DO_NOT_USE_WILL_BREAK__handleAlarmMemoryLimit(options: {
207
- readonly callbacks: ReadonlyArray<string>;
208
- readonly sealed: boolean;
209
- readonly nextTime?: number;
210
- }): void;
208
+ /** @internal Remove schedules owned by one routed Lifecycle subtree. */
209
+ __DO_NOT_USE_WILL_BREAK__cleanupRoutePrefix(prefix: string): Promise<void>;
211
210
  }
212
211
  //#endregion
213
212
  export {
@@ -221,4 +220,4 @@ export {
221
220
  SchedulerCallbacks as s,
222
221
  Scheduler as t
223
222
  };
224
- //# sourceMappingURL=scheduler-DQoTGoAW.d.ts.map
223
+ //# sourceMappingURL=scheduler-BGq6M5Kd.d.ts.map