@holon-run/agentinbox 0.1.0 → 0.1.3

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/dist/src/http.js CHANGED
@@ -5,23 +5,10 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
5
5
  Object.defineProperty(exports, "__esModule", { value: true });
6
6
  exports.createServer = createServer;
7
7
  const node_http_1 = __importDefault(require("node:http"));
8
+ const fastify_1 = __importDefault(require("fastify"));
9
+ const swagger_1 = __importDefault(require("@fastify/swagger"));
10
+ const current_agent_1 = require("./current_agent");
8
11
  const util_1 = require("./util");
9
- async function readJson(req) {
10
- const chunks = [];
11
- for await (const chunk of req) {
12
- chunks.push(Buffer.from(chunk));
13
- }
14
- const body = Buffer.concat(chunks).toString("utf8");
15
- if (!body) {
16
- return {};
17
- }
18
- return (0, util_1.asObject)(JSON.parse(body));
19
- }
20
- function send(res, statusCode, data) {
21
- res.statusCode = statusCode;
22
- res.setHeader("content-type", "application/json; charset=utf-8");
23
- res.end((0, util_1.jsonResponse)(data));
24
- }
25
12
  function sendSse(res, event, data) {
26
13
  res.write(`event: ${event}\n`);
27
14
  const payload = (0, util_1.jsonResponse)(data)
@@ -30,379 +17,1000 @@ function sendSse(res, event, data) {
30
17
  .join("\n");
31
18
  res.write(`${payload}\n\n`);
32
19
  }
33
- function createServer(service) {
34
- return node_http_1.default.createServer(async (req, res) => {
35
- try {
36
- if (!req.url || !req.method) {
37
- send(res, 400, { error: "missing request metadata" });
38
- return;
39
- }
40
- const url = new URL(req.url, "http://127.0.0.1");
41
- if (req.method === "GET" && url.pathname === "/healthz") {
42
- send(res, 200, { ok: true });
43
- return;
44
- }
45
- if (req.method === "GET" && url.pathname === "/status") {
46
- send(res, 200, service.status());
47
- return;
48
- }
49
- if (req.method === "POST" && url.pathname === "/gc") {
50
- send(res, 200, service.gc());
51
- return;
52
- }
53
- if (req.method === "GET" && url.pathname === "/sources") {
54
- send(res, 200, { sources: service.listSources() });
55
- return;
56
- }
57
- if (req.method === "POST" && url.pathname === "/sources/register") {
58
- send(res, 200, await service.registerSource(await readJson(req)));
59
- return;
60
- }
61
- const sourceMatch = url.pathname.match(/^\/sources\/([^/]+)$/);
62
- if (req.method === "GET" && sourceMatch) {
63
- send(res, 200, service.getSourceDetails(decodeURIComponent(sourceMatch[1])));
64
- return;
65
- }
66
- const sourceSchemaMatch = url.pathname.match(/^\/source-types\/([^/]+)\/schema$/);
67
- if (req.method === "GET" && sourceSchemaMatch) {
68
- send(res, 200, service.getSourceSchema(decodeURIComponent(sourceSchemaMatch[1])));
69
- return;
70
- }
71
- const sourcePollMatch = url.pathname.match(/^\/sources\/([^/]+)\/poll$/);
72
- if (req.method === "POST" && sourcePollMatch) {
73
- send(res, 200, await service.pollSource(decodeURIComponent(sourcePollMatch[1])));
74
- return;
75
- }
76
- const sourceEventsMatch = url.pathname.match(/^\/sources\/([^/]+)\/events$/);
77
- if (req.method === "POST" && sourceEventsMatch) {
78
- const body = await readJson(req);
79
- const sourceId = decodeURIComponent(sourceEventsMatch[1]);
80
- const sourceNativeId = parseRequiredString(body.sourceNativeId, "sources/events requires sourceNativeId");
81
- const eventVariant = parseRequiredString(body.eventVariant, "sources/events requires eventVariant");
82
- send(res, 200, await service.appendSourceEventByCaller(sourceId, {
83
- sourceNativeId,
84
- eventVariant,
85
- occurredAt: parseOptionalString(body.occurredAt),
86
- metadata: (0, util_1.asObject)(body.metadata),
87
- rawPayload: (0, util_1.asObject)(body.rawPayload),
88
- deliveryHandle: parseOptionalDeliveryHandle(body.deliveryHandle),
89
- }));
90
- return;
91
- }
92
- if (req.method === "GET" && url.pathname === "/agents") {
93
- send(res, 200, { agents: service.listAgents() });
94
- return;
95
- }
96
- if (req.method === "POST" && url.pathname === "/agents/register") {
97
- const body = await readJson(req);
98
- const backend = parseRequiredString(body.backend, "agents/register requires backend");
99
- send(res, 200, service.registerAgent({
100
- agentId: parseOptionalString(body.agentId) ?? null,
101
- forceRebind: parseOptionalBoolean(body.forceRebind) ?? false,
102
- backend: backend,
103
- runtimeKind: parseOptionalString(body.runtimeKind),
104
- runtimeSessionId: parseOptionalString(body.runtimeSessionId),
105
- mode: "agent_prompt",
106
- tmuxPaneId: parseOptionalString(body.tmuxPaneId),
107
- tty: parseOptionalString(body.tty),
108
- termProgram: parseOptionalString(body.termProgram),
109
- itermSessionId: parseOptionalString(body.itermSessionId),
110
- notifyLeaseMs: parseOptionalInteger(body.notifyLeaseMs) ?? null,
111
- }));
112
- return;
113
- }
114
- const agentMatch = url.pathname.match(/^\/agents\/([^/]+)$/);
115
- if (req.method === "GET" && agentMatch) {
116
- send(res, 200, service.getAgentDetails(decodeURIComponent(agentMatch[1])));
117
- return;
118
- }
119
- if (req.method === "DELETE" && agentMatch) {
120
- send(res, 200, service.removeAgent(decodeURIComponent(agentMatch[1])));
121
- return;
122
- }
123
- const agentTargetsMatch = url.pathname.match(/^\/agents\/([^/]+)\/targets$/);
124
- if (req.method === "GET" && agentTargetsMatch) {
125
- send(res, 200, {
126
- targets: service.listActivationTargets(decodeURIComponent(agentTargetsMatch[1])),
127
- });
128
- return;
129
- }
130
- if (req.method === "POST" && agentTargetsMatch) {
131
- const body = await readJson(req);
132
- const agentId = decodeURIComponent(agentTargetsMatch[1]);
133
- const kind = parseRequiredString(body.kind, "agents/targets requires kind");
134
- if (kind !== "webhook") {
135
- send(res, 400, { error: `unsupported activation target kind: ${kind}` });
136
- return;
137
- }
138
- const urlValue = parseRequiredString(body.url, "agents/targets requires url");
139
- send(res, 200, service.addWebhookActivationTarget(agentId, {
140
- url: urlValue,
141
- activationMode: parseOptionalString(body.activationMode),
142
- notifyLeaseMs: parseOptionalInteger(body.notifyLeaseMs) ?? null,
143
- }));
144
- return;
145
- }
146
- const agentTargetMatch = url.pathname.match(/^\/agents\/([^/]+)\/targets\/([^/]+)$/);
147
- if (req.method === "DELETE" && agentTargetMatch) {
148
- send(res, 200, service.removeActivationTarget(decodeURIComponent(agentTargetMatch[1]), decodeURIComponent(agentTargetMatch[2])));
149
- return;
150
- }
151
- if (req.method === "GET" && url.pathname === "/subscriptions") {
152
- send(res, 200, {
153
- subscriptions: service.listSubscriptions({
154
- sourceId: url.searchParams.get("source_id") ?? undefined,
155
- agentId: url.searchParams.get("agent_id") ?? undefined,
156
- }),
157
- });
158
- return;
159
- }
160
- if (req.method === "POST" && url.pathname === "/subscriptions/register") {
161
- const body = await readJson(req);
162
- send(res, 200, await service.registerSubscription({
163
- agentId: parseRequiredString(body.agentId, "subscriptions/register requires agentId"),
164
- sourceId: parseRequiredString(body.sourceId, "subscriptions/register requires sourceId"),
165
- filter: (0, util_1.asObject)(body.filter),
166
- lifecycleMode: parseOptionalString(body.lifecycleMode),
167
- expiresAt: parseOptionalString(body.expiresAt) ?? null,
168
- startPolicy: parseOptionalString(body.startPolicy),
169
- startOffset: parseOptionalInteger(body.startOffset),
170
- startTime: parseOptionalString(body.startTime) ?? undefined,
171
- }));
172
- return;
173
- }
174
- const subscriptionMatch = url.pathname.match(/^\/subscriptions\/([^/]+)$/);
175
- if (req.method === "GET" && subscriptionMatch) {
176
- send(res, 200, await service.getSubscriptionDetails(decodeURIComponent(subscriptionMatch[1])));
177
- return;
178
- }
179
- if (req.method === "DELETE" && subscriptionMatch) {
180
- send(res, 200, await service.removeSubscription(decodeURIComponent(subscriptionMatch[1])));
181
- return;
182
- }
183
- const subscriptionPollMatch = url.pathname.match(/^\/subscriptions\/([^/]+)\/poll$/);
184
- if (req.method === "POST" && subscriptionPollMatch) {
185
- send(res, 200, await service.pollSubscription(decodeURIComponent(subscriptionPollMatch[1])));
186
- return;
187
- }
188
- const subscriptionLagMatch = url.pathname.match(/^\/subscriptions\/([^/]+)\/lag$/);
189
- if (req.method === "GET" && subscriptionLagMatch) {
190
- send(res, 200, await service.getSubscriptionLag(decodeURIComponent(subscriptionLagMatch[1])));
191
- return;
192
- }
193
- const subscriptionResetMatch = url.pathname.match(/^\/subscriptions\/([^/]+)\/reset$/);
194
- if (req.method === "POST" && subscriptionResetMatch) {
195
- const body = await readJson(req);
196
- const startPolicy = parseRequiredString(body.startPolicy, "subscriptions/reset requires startPolicy");
197
- send(res, 200, await service.resetSubscription({
198
- subscriptionId: decodeURIComponent(subscriptionResetMatch[1]),
199
- startPolicy: startPolicy,
200
- startOffset: parseOptionalInteger(body.startOffset),
201
- startTime: parseOptionalString(body.startTime) ?? null,
202
- }));
203
- return;
204
- }
205
- if (req.method === "POST" && url.pathname === "/fixtures/emit") {
206
- const body = await readJson(req);
207
- const sourceId = parseRequiredString(body.sourceId, "fixtures/emit requires sourceId");
208
- const sourceNativeId = parseRequiredString(body.sourceNativeId, "fixtures/emit requires sourceNativeId");
209
- const eventVariant = parseRequiredString(body.eventVariant, "fixtures/emit requires eventVariant");
210
- send(res, 200, await service.appendFixtureEvent(sourceId, {
211
- sourceNativeId,
212
- eventVariant,
213
- occurredAt: parseOptionalString(body.occurredAt),
214
- metadata: (0, util_1.asObject)(body.metadata),
215
- rawPayload: (0, util_1.asObject)(body.rawPayload),
216
- deliveryHandle: parseOptionalDeliveryHandle(body.deliveryHandle),
217
- }));
218
- return;
219
- }
220
- const agentInboxMatch = url.pathname.match(/^\/agents\/([^/]+)\/inbox$/);
221
- if (req.method === "GET" && agentInboxMatch) {
222
- send(res, 200, service.getInboxDetailsByAgent(decodeURIComponent(agentInboxMatch[1])));
223
- return;
224
- }
225
- const agentInboxItemsMatch = url.pathname.match(/^\/agents\/([^/]+)\/inbox\/items$/);
226
- if (req.method === "GET" && agentInboxItemsMatch) {
227
- send(res, 200, {
228
- items: service.listInboxItems(decodeURIComponent(agentInboxItemsMatch[1]), {
229
- afterItemId: url.searchParams.get("after_item_id") ?? undefined,
230
- includeAcked: url.searchParams.has("include_acked")
231
- ? url.searchParams.get("include_acked") === "true"
232
- : undefined,
233
- }),
234
- });
235
- return;
236
- }
237
- const agentInboxWatchMatch = url.pathname.match(/^\/agents\/([^/]+)\/inbox\/watch$/);
238
- if (req.method === "GET" && agentInboxWatchMatch) {
239
- const agentId = decodeURIComponent(agentInboxWatchMatch[1]);
240
- const watchOptions = {
241
- afterItemId: url.searchParams.get("after_item_id") ?? undefined,
242
- includeAcked: url.searchParams.has("include_acked")
243
- ? url.searchParams.get("include_acked") === "true"
244
- : undefined,
245
- heartbeatMs: url.searchParams.has("heartbeat_ms")
246
- ? parsePositiveInteger(url.searchParams.get("heartbeat_ms"))
247
- : undefined,
248
- };
249
- res.writeHead(200, {
250
- "content-type": "text/event-stream; charset=utf-8",
251
- "cache-control": "no-cache, no-transform",
252
- connection: "keep-alive",
253
- });
254
- const session = service.watchInbox(agentId, watchOptions, (event) => {
255
- sendSse(res, event.event, event);
256
- });
257
- sendSse(res, "items", {
258
- event: "items",
259
- agentId,
260
- items: session.initialItems,
261
- });
262
- session.start();
263
- const heartbeatMs = watchOptions.heartbeatMs ?? 15_000;
264
- const heartbeat = setInterval(() => {
265
- sendSse(res, "heartbeat", {
266
- event: "heartbeat",
267
- agentId,
268
- timestamp: new Date().toISOString(),
269
- });
270
- }, heartbeatMs);
271
- const cleanup = () => {
272
- clearInterval(heartbeat);
273
- session.close();
274
- };
275
- req.on("close", cleanup);
276
- req.on("error", cleanup);
277
- return;
278
- }
279
- const agentInboxAckAllMatch = url.pathname.match(/^\/agents\/([^/]+)\/inbox\/ack-all$/);
280
- if (req.method === "POST" && agentInboxAckAllMatch) {
281
- send(res, 200, service.ackAllInboxItems(decodeURIComponent(agentInboxAckAllMatch[1])));
282
- return;
283
- }
284
- const agentInboxCompactMatch = url.pathname.match(/^\/agents\/([^/]+)\/inbox\/compact$/);
285
- if (req.method === "POST" && agentInboxCompactMatch) {
286
- send(res, 200, service.compactInbox(decodeURIComponent(agentInboxCompactMatch[1])));
287
- return;
288
- }
289
- const agentInboxAckMatch = url.pathname.match(/^\/agents\/([^/]+)\/inbox\/ack$/);
290
- if (req.method === "POST" && agentInboxAckMatch) {
291
- const body = await readJson(req);
292
- const itemIds = Array.isArray(body.itemIds) ? body.itemIds.map((itemId) => String(itemId)) : [];
293
- const throughItemId = parseOptionalString(body.throughItemId);
294
- if (throughItemId && itemIds.length > 0) {
295
- send(res, 400, { error: "inbox/ack accepts either itemIds or throughItemId" });
296
- return;
20
+ const jsonObjectSchema = {
21
+ type: "object",
22
+ additionalProperties: true,
23
+ };
24
+ const errorResponseSchema = {
25
+ type: "object",
26
+ required: ["error"],
27
+ additionalProperties: false,
28
+ properties: {
29
+ error: { type: "string" },
30
+ },
31
+ };
32
+ const deliveryHandleSchema = {
33
+ type: "object",
34
+ additionalProperties: false,
35
+ required: ["provider", "surface", "targetRef"],
36
+ properties: {
37
+ provider: { type: "string", minLength: 1 },
38
+ surface: { type: "string", minLength: 1 },
39
+ targetRef: { type: "string", minLength: 1 },
40
+ threadRef: { type: "string" },
41
+ replyMode: { type: "string" },
42
+ },
43
+ };
44
+ function buildFastifyServer(service) {
45
+ const app = (0, fastify_1.default)({
46
+ logger: false,
47
+ ajv: {
48
+ customOptions: {
49
+ coerceTypes: false,
50
+ },
51
+ },
52
+ });
53
+ void app.register(swagger_1.default, {
54
+ openapi: {
55
+ info: {
56
+ title: "AgentInbox Control Plane API",
57
+ version: "0.1.0",
58
+ },
59
+ },
60
+ });
61
+ app.get("/healthz", {
62
+ schema: {
63
+ tags: ["system"],
64
+ response: {
65
+ 200: {
66
+ type: "object",
67
+ required: ["ok"],
68
+ properties: {
69
+ ok: { type: "boolean" },
70
+ },
71
+ },
72
+ },
73
+ },
74
+ }, async () => ({ ok: true }));
75
+ app.get("/status", {
76
+ schema: {
77
+ tags: ["system"],
78
+ response: {
79
+ 200: jsonObjectSchema,
80
+ },
81
+ },
82
+ }, async () => service.status());
83
+ app.get("/openapi.json", {
84
+ schema: {
85
+ tags: ["system"],
86
+ response: {
87
+ 200: jsonObjectSchema,
88
+ },
89
+ },
90
+ }, async () => app.swagger());
91
+ app.post("/gc", {
92
+ schema: {
93
+ tags: ["inbox"],
94
+ response: {
95
+ 200: jsonObjectSchema,
96
+ },
97
+ },
98
+ }, async () => service.gc());
99
+ app.get("/sources", {
100
+ schema: {
101
+ tags: ["sources"],
102
+ response: {
103
+ 200: {
104
+ type: "object",
105
+ required: ["sources"],
106
+ properties: {
107
+ sources: { type: "array", items: jsonObjectSchema },
108
+ },
109
+ },
110
+ },
111
+ },
112
+ }, async () => ({ sources: service.listSources() }));
113
+ app.post("/sources", {
114
+ schema: {
115
+ tags: ["sources"],
116
+ body: {
117
+ type: "object",
118
+ additionalProperties: false,
119
+ required: ["sourceType", "sourceKey"],
120
+ properties: {
121
+ sourceType: { type: "string", minLength: 1 },
122
+ sourceKey: { type: "string", minLength: 1 },
123
+ configRef: { anyOf: [{ type: "string" }, { type: "null" }] },
124
+ config: jsonObjectSchema,
125
+ },
126
+ },
127
+ response: {
128
+ 200: jsonObjectSchema,
129
+ 400: errorResponseSchema,
130
+ },
131
+ },
132
+ }, async (request) => service.registerSource(request.body));
133
+ app.get("/sources/:sourceId", {
134
+ schema: {
135
+ tags: ["sources"],
136
+ params: {
137
+ type: "object",
138
+ required: ["sourceId"],
139
+ properties: {
140
+ sourceId: { type: "string", minLength: 1 },
141
+ },
142
+ },
143
+ response: {
144
+ 200: jsonObjectSchema,
145
+ 404: errorResponseSchema,
146
+ },
147
+ },
148
+ }, async (request) => {
149
+ const params = request.params;
150
+ return service.getSourceDetails(decodeURIComponent(params.sourceId));
151
+ });
152
+ app.delete("/sources/:sourceId", {
153
+ schema: {
154
+ tags: ["sources"],
155
+ params: {
156
+ type: "object",
157
+ required: ["sourceId"],
158
+ properties: {
159
+ sourceId: { type: "string", minLength: 1 },
160
+ },
161
+ },
162
+ response: {
163
+ 200: jsonObjectSchema,
164
+ 400: errorResponseSchema,
165
+ },
166
+ },
167
+ }, async (request) => {
168
+ const params = request.params;
169
+ return service.removeSource(decodeURIComponent(params.sourceId));
170
+ });
171
+ app.patch("/sources/:sourceId", {
172
+ schema: {
173
+ tags: ["sources"],
174
+ params: {
175
+ type: "object",
176
+ required: ["sourceId"],
177
+ properties: {
178
+ sourceId: { type: "string", minLength: 1 },
179
+ },
180
+ },
181
+ body: {
182
+ type: "object",
183
+ additionalProperties: false,
184
+ minProperties: 1,
185
+ properties: {
186
+ configRef: { anyOf: [{ type: "string" }, { type: "null" }] },
187
+ config: jsonObjectSchema,
188
+ },
189
+ },
190
+ response: {
191
+ 200: jsonObjectSchema,
192
+ 400: errorResponseSchema,
193
+ },
194
+ },
195
+ }, async (request) => {
196
+ const params = request.params;
197
+ return service.updateSource(decodeURIComponent(params.sourceId), request.body);
198
+ });
199
+ app.post("/sources/:sourceId/pause", {
200
+ schema: {
201
+ tags: ["sources"],
202
+ params: {
203
+ type: "object",
204
+ required: ["sourceId"],
205
+ properties: {
206
+ sourceId: { type: "string", minLength: 1 },
207
+ },
208
+ },
209
+ response: {
210
+ 200: jsonObjectSchema,
211
+ },
212
+ },
213
+ }, async (request) => {
214
+ const params = request.params;
215
+ return service.pauseSource(decodeURIComponent(params.sourceId));
216
+ });
217
+ app.post("/sources/:sourceId/resume", {
218
+ schema: {
219
+ tags: ["sources"],
220
+ params: {
221
+ type: "object",
222
+ required: ["sourceId"],
223
+ properties: {
224
+ sourceId: { type: "string", minLength: 1 },
225
+ },
226
+ },
227
+ response: {
228
+ 200: jsonObjectSchema,
229
+ },
230
+ },
231
+ }, async (request) => {
232
+ const params = request.params;
233
+ return service.resumeSource(decodeURIComponent(params.sourceId));
234
+ });
235
+ app.get("/source-types/:sourceType/schema", {
236
+ schema: {
237
+ tags: ["sources"],
238
+ params: {
239
+ type: "object",
240
+ required: ["sourceType"],
241
+ properties: {
242
+ sourceType: { type: "string", minLength: 1 },
243
+ },
244
+ },
245
+ response: {
246
+ 200: jsonObjectSchema,
247
+ },
248
+ },
249
+ }, async (request) => {
250
+ const params = request.params;
251
+ return service.getSourceSchema(decodeURIComponent(params.sourceType));
252
+ });
253
+ app.post("/sources/:sourceId/poll", {
254
+ schema: {
255
+ tags: ["sources"],
256
+ params: {
257
+ type: "object",
258
+ required: ["sourceId"],
259
+ properties: {
260
+ sourceId: { type: "string", minLength: 1 },
261
+ },
262
+ },
263
+ response: {
264
+ 200: jsonObjectSchema,
265
+ },
266
+ },
267
+ }, async (request) => {
268
+ const params = request.params;
269
+ return service.pollSource(decodeURIComponent(params.sourceId));
270
+ });
271
+ app.post("/sources/:sourceId/events", {
272
+ schema: {
273
+ tags: ["sources"],
274
+ params: {
275
+ type: "object",
276
+ required: ["sourceId"],
277
+ properties: {
278
+ sourceId: { type: "string", minLength: 1 },
279
+ },
280
+ },
281
+ body: {
282
+ type: "object",
283
+ additionalProperties: false,
284
+ required: ["sourceNativeId", "eventVariant"],
285
+ properties: {
286
+ sourceNativeId: { type: "string", minLength: 1 },
287
+ eventVariant: { type: "string", minLength: 1 },
288
+ occurredAt: { type: "string", minLength: 1 },
289
+ metadata: jsonObjectSchema,
290
+ rawPayload: jsonObjectSchema,
291
+ deliveryHandle: deliveryHandleSchema,
292
+ },
293
+ },
294
+ response: {
295
+ 200: jsonObjectSchema,
296
+ 400: errorResponseSchema,
297
+ },
298
+ },
299
+ }, async (request) => {
300
+ const params = request.params;
301
+ const body = request.body;
302
+ return service.appendSourceEventByCaller(decodeURIComponent(params.sourceId), {
303
+ sourceNativeId: body.sourceNativeId,
304
+ eventVariant: body.eventVariant,
305
+ occurredAt: optionalString(body.occurredAt),
306
+ metadata: body.metadata ?? {},
307
+ rawPayload: body.rawPayload ?? {},
308
+ deliveryHandle: body.deliveryHandle ?? null,
309
+ });
310
+ });
311
+ app.get("/agents", {
312
+ schema: {
313
+ tags: ["agents"],
314
+ querystring: {
315
+ type: "object",
316
+ additionalProperties: false,
317
+ properties: {
318
+ include_targets: { type: "string", enum: ["true", "false"] },
319
+ },
320
+ },
321
+ response: {
322
+ 200: {
323
+ type: "object",
324
+ required: ["agents"],
325
+ properties: {
326
+ agents: { type: "array", items: jsonObjectSchema },
327
+ },
328
+ },
329
+ },
330
+ },
331
+ }, async (request) => {
332
+ const query = request.query;
333
+ const agents = service.listAgents();
334
+ if (query.include_targets === "true") {
335
+ const activationTargetsByAgentId = service.listActivationTargets().reduce((grouped, target) => {
336
+ const targets = grouped.get(target.agentId);
337
+ const summarized = (0, current_agent_1.summarizeActivationTarget)(target);
338
+ if (targets) {
339
+ targets.push(summarized);
297
340
  }
298
- if (throughItemId) {
299
- send(res, 200, service.ackInboxItemsThrough(decodeURIComponent(agentInboxAckMatch[1]), throughItemId));
300
- return;
341
+ else {
342
+ grouped.set(target.agentId, [summarized]);
301
343
  }
302
- send(res, 200, service.ackInboxItems(decodeURIComponent(agentInboxAckMatch[1]), itemIds));
303
- return;
304
- }
305
- if (req.method === "POST" && url.pathname === "/deliveries/send") {
306
- send(res, 200, await service.sendDelivery(await readJson(req)));
307
- return;
308
- }
309
- send(res, 404, { error: "not found" });
344
+ return grouped;
345
+ }, new Map());
346
+ return {
347
+ agents: agents.map((agent) => ({
348
+ agent,
349
+ activationTargets: activationTargetsByAgentId.get(agent.agentId) ?? [],
350
+ })),
351
+ };
310
352
  }
311
- catch (error) {
312
- if (error instanceof SyntaxError) {
313
- send(res, 400, { error: error.message });
314
- return;
315
- }
316
- const message = error instanceof Error ? error.message : String(error);
317
- if (message.startsWith("unknown ")) {
318
- send(res, 404, { error: message });
319
- return;
320
- }
321
- if (isBadRequestError(message)) {
322
- send(res, 400, { error: message });
323
- return;
324
- }
325
- send(res, 500, { error: message });
353
+ return { agents };
354
+ });
355
+ app.post("/agents", {
356
+ schema: {
357
+ tags: ["agents"],
358
+ body: {
359
+ type: "object",
360
+ additionalProperties: false,
361
+ required: ["backend"],
362
+ properties: {
363
+ agentId: { type: "string" },
364
+ forceRebind: { type: "boolean" },
365
+ backend: { type: "string", minLength: 1 },
366
+ runtimeKind: { type: "string" },
367
+ runtimeSessionId: { type: "string" },
368
+ tmuxPaneId: { type: "string" },
369
+ tty: { type: "string" },
370
+ termProgram: { type: "string" },
371
+ itermSessionId: { type: "string" },
372
+ notifyLeaseMs: { type: "integer", minimum: 1 },
373
+ },
374
+ },
375
+ response: {
376
+ 200: jsonObjectSchema,
377
+ 400: errorResponseSchema,
378
+ },
379
+ },
380
+ }, async (request) => {
381
+ const body = request.body;
382
+ return service.registerAgent({
383
+ agentId: optionalString(body.agentId) ?? null,
384
+ forceRebind: body.forceRebind === true,
385
+ backend: String(body.backend),
386
+ runtimeKind: optionalString(body.runtimeKind),
387
+ runtimeSessionId: optionalString(body.runtimeSessionId),
388
+ mode: "agent_prompt",
389
+ tmuxPaneId: optionalString(body.tmuxPaneId),
390
+ tty: optionalString(body.tty),
391
+ termProgram: optionalString(body.termProgram),
392
+ itermSessionId: optionalString(body.itermSessionId),
393
+ notifyLeaseMs: typeof body.notifyLeaseMs === "number" ? body.notifyLeaseMs : null,
394
+ });
395
+ });
396
+ app.get("/agents/:agentId", {
397
+ schema: {
398
+ tags: ["agents"],
399
+ params: {
400
+ type: "object",
401
+ required: ["agentId"],
402
+ properties: {
403
+ agentId: { type: "string", minLength: 1 },
404
+ },
405
+ },
406
+ response: {
407
+ 200: jsonObjectSchema,
408
+ 404: errorResponseSchema,
409
+ },
410
+ },
411
+ }, async (request) => {
412
+ const params = request.params;
413
+ return service.getAgentDetails(decodeURIComponent(params.agentId));
414
+ });
415
+ app.delete("/agents/:agentId", {
416
+ schema: {
417
+ tags: ["agents"],
418
+ params: {
419
+ type: "object",
420
+ required: ["agentId"],
421
+ properties: {
422
+ agentId: { type: "string", minLength: 1 },
423
+ },
424
+ },
425
+ response: {
426
+ 200: jsonObjectSchema,
427
+ 404: errorResponseSchema,
428
+ },
429
+ },
430
+ }, async (request) => {
431
+ const params = request.params;
432
+ return service.removeAgent(decodeURIComponent(params.agentId));
433
+ });
434
+ app.get("/agents/:agentId/targets", {
435
+ schema: {
436
+ tags: ["agents"],
437
+ params: {
438
+ type: "object",
439
+ required: ["agentId"],
440
+ properties: {
441
+ agentId: { type: "string", minLength: 1 },
442
+ },
443
+ },
444
+ response: {
445
+ 200: {
446
+ type: "object",
447
+ required: ["targets"],
448
+ properties: {
449
+ targets: { type: "array", items: jsonObjectSchema },
450
+ },
451
+ },
452
+ },
453
+ },
454
+ }, async (request) => {
455
+ const params = request.params;
456
+ return { targets: service.listActivationTargets(decodeURIComponent(params.agentId)) };
457
+ });
458
+ app.post("/agents/:agentId/targets", {
459
+ schema: {
460
+ tags: ["agents"],
461
+ params: {
462
+ type: "object",
463
+ required: ["agentId"],
464
+ properties: {
465
+ agentId: { type: "string", minLength: 1 },
466
+ },
467
+ },
468
+ body: {
469
+ type: "object",
470
+ additionalProperties: false,
471
+ required: ["kind", "url"],
472
+ properties: {
473
+ kind: { type: "string", enum: ["webhook"] },
474
+ url: { type: "string", minLength: 1 },
475
+ activationMode: { type: "string" },
476
+ notifyLeaseMs: { type: "integer", minimum: 1 },
477
+ },
478
+ },
479
+ response: {
480
+ 200: jsonObjectSchema,
481
+ 400: errorResponseSchema,
482
+ },
483
+ },
484
+ }, async (request) => {
485
+ const params = request.params;
486
+ const body = request.body;
487
+ return service.addWebhookActivationTarget(decodeURIComponent(params.agentId), {
488
+ url: body.url,
489
+ activationMode: body.activationMode,
490
+ notifyLeaseMs: body.notifyLeaseMs ?? null,
491
+ });
492
+ });
493
+ app.delete("/agents/:agentId/targets/:targetId", {
494
+ schema: {
495
+ tags: ["agents"],
496
+ params: {
497
+ type: "object",
498
+ required: ["agentId", "targetId"],
499
+ properties: {
500
+ agentId: { type: "string", minLength: 1 },
501
+ targetId: { type: "string", minLength: 1 },
502
+ },
503
+ },
504
+ response: {
505
+ 200: jsonObjectSchema,
506
+ },
507
+ },
508
+ }, async (request) => {
509
+ const params = request.params;
510
+ return service.removeActivationTarget(decodeURIComponent(params.agentId), decodeURIComponent(params.targetId));
511
+ });
512
+ app.get("/subscriptions", {
513
+ schema: {
514
+ tags: ["subscriptions"],
515
+ querystring: {
516
+ type: "object",
517
+ additionalProperties: false,
518
+ properties: {
519
+ source_id: { type: "string" },
520
+ agent_id: { type: "string" },
521
+ },
522
+ },
523
+ response: {
524
+ 200: {
525
+ type: "object",
526
+ required: ["subscriptions"],
527
+ properties: {
528
+ subscriptions: { type: "array", items: jsonObjectSchema },
529
+ },
530
+ },
531
+ },
532
+ },
533
+ }, async (request) => {
534
+ const query = request.query;
535
+ return {
536
+ subscriptions: service.listSubscriptions({
537
+ sourceId: query.source_id,
538
+ agentId: query.agent_id,
539
+ }),
540
+ };
541
+ });
542
+ app.post("/subscriptions", {
543
+ schema: {
544
+ tags: ["subscriptions"],
545
+ body: {
546
+ type: "object",
547
+ additionalProperties: false,
548
+ required: ["agentId", "sourceId"],
549
+ properties: {
550
+ agentId: { type: "string", minLength: 1 },
551
+ sourceId: { type: "string", minLength: 1 },
552
+ filter: jsonObjectSchema,
553
+ lifecycleMode: { type: "string" },
554
+ expiresAt: { type: "string" },
555
+ startPolicy: { type: "string" },
556
+ startOffset: { type: "integer" },
557
+ startTime: { type: "string" },
558
+ },
559
+ },
560
+ response: {
561
+ 200: jsonObjectSchema,
562
+ 400: errorResponseSchema,
563
+ },
564
+ },
565
+ }, async (request) => {
566
+ const body = request.body;
567
+ return service.registerSubscription({
568
+ agentId: String(body.agentId),
569
+ sourceId: String(body.sourceId),
570
+ filter: body.filter ?? {},
571
+ lifecycleMode: optionalString(body.lifecycleMode),
572
+ expiresAt: optionalString(body.expiresAt) ?? null,
573
+ startPolicy: optionalString(body.startPolicy),
574
+ startOffset: typeof body.startOffset === "number" ? body.startOffset : undefined,
575
+ startTime: optionalString(body.startTime) ?? undefined,
576
+ });
577
+ });
578
+ app.get("/subscriptions/:subscriptionId", {
579
+ schema: {
580
+ tags: ["subscriptions"],
581
+ params: {
582
+ type: "object",
583
+ required: ["subscriptionId"],
584
+ properties: {
585
+ subscriptionId: { type: "string", minLength: 1 },
586
+ },
587
+ },
588
+ response: {
589
+ 200: jsonObjectSchema,
590
+ 404: errorResponseSchema,
591
+ },
592
+ },
593
+ }, async (request) => {
594
+ const params = request.params;
595
+ return service.getSubscriptionDetails(decodeURIComponent(params.subscriptionId));
596
+ });
597
+ app.delete("/subscriptions/:subscriptionId", {
598
+ schema: {
599
+ tags: ["subscriptions"],
600
+ params: {
601
+ type: "object",
602
+ required: ["subscriptionId"],
603
+ properties: {
604
+ subscriptionId: { type: "string", minLength: 1 },
605
+ },
606
+ },
607
+ response: {
608
+ 200: jsonObjectSchema,
609
+ 404: errorResponseSchema,
610
+ },
611
+ },
612
+ }, async (request) => {
613
+ const params = request.params;
614
+ return service.removeSubscription(decodeURIComponent(params.subscriptionId));
615
+ });
616
+ app.post("/subscriptions/:subscriptionId/poll", {
617
+ schema: {
618
+ tags: ["subscriptions"],
619
+ params: {
620
+ type: "object",
621
+ required: ["subscriptionId"],
622
+ properties: {
623
+ subscriptionId: { type: "string", minLength: 1 },
624
+ },
625
+ },
626
+ response: {
627
+ 200: jsonObjectSchema,
628
+ },
629
+ },
630
+ }, async (request) => {
631
+ const params = request.params;
632
+ return service.pollSubscription(decodeURIComponent(params.subscriptionId));
633
+ });
634
+ app.get("/subscriptions/:subscriptionId/lag", {
635
+ schema: {
636
+ tags: ["subscriptions"],
637
+ params: {
638
+ type: "object",
639
+ required: ["subscriptionId"],
640
+ properties: {
641
+ subscriptionId: { type: "string", minLength: 1 },
642
+ },
643
+ },
644
+ response: {
645
+ 200: jsonObjectSchema,
646
+ },
647
+ },
648
+ }, async (request) => {
649
+ const params = request.params;
650
+ return service.getSubscriptionLag(decodeURIComponent(params.subscriptionId));
651
+ });
652
+ app.post("/subscriptions/:subscriptionId/reset", {
653
+ schema: {
654
+ tags: ["subscriptions"],
655
+ params: {
656
+ type: "object",
657
+ required: ["subscriptionId"],
658
+ properties: {
659
+ subscriptionId: { type: "string", minLength: 1 },
660
+ },
661
+ },
662
+ body: {
663
+ type: "object",
664
+ additionalProperties: false,
665
+ required: ["startPolicy"],
666
+ properties: {
667
+ startPolicy: { type: "string", minLength: 1 },
668
+ startOffset: { type: "integer" },
669
+ startTime: { type: "string" },
670
+ },
671
+ },
672
+ response: {
673
+ 200: jsonObjectSchema,
674
+ 400: errorResponseSchema,
675
+ },
676
+ },
677
+ }, async (request) => {
678
+ const params = request.params;
679
+ const body = request.body;
680
+ return service.resetSubscription({
681
+ subscriptionId: decodeURIComponent(params.subscriptionId),
682
+ startPolicy: body.startPolicy,
683
+ startOffset: body.startOffset,
684
+ startTime: body.startTime ?? null,
685
+ });
686
+ });
687
+ app.get("/agents/:agentId/inbox", {
688
+ schema: {
689
+ tags: ["inbox"],
690
+ params: {
691
+ type: "object",
692
+ required: ["agentId"],
693
+ properties: {
694
+ agentId: { type: "string", minLength: 1 },
695
+ },
696
+ },
697
+ response: {
698
+ 200: jsonObjectSchema,
699
+ 404: errorResponseSchema,
700
+ },
701
+ },
702
+ }, async (request) => {
703
+ const params = request.params;
704
+ return service.getInboxDetailsByAgent(decodeURIComponent(params.agentId));
705
+ });
706
+ app.get("/agents/:agentId/inbox/items", {
707
+ schema: {
708
+ tags: ["inbox"],
709
+ params: {
710
+ type: "object",
711
+ required: ["agentId"],
712
+ properties: {
713
+ agentId: { type: "string", minLength: 1 },
714
+ },
715
+ },
716
+ querystring: {
717
+ type: "object",
718
+ additionalProperties: false,
719
+ properties: {
720
+ after_item_id: { type: "string" },
721
+ include_acked: { type: "string", enum: ["true", "false"] },
722
+ },
723
+ },
724
+ response: {
725
+ 200: {
726
+ type: "object",
727
+ required: ["items"],
728
+ properties: {
729
+ items: { type: "array", items: jsonObjectSchema },
730
+ },
731
+ },
732
+ },
733
+ },
734
+ }, async (request) => {
735
+ const params = request.params;
736
+ const query = request.query;
737
+ return {
738
+ items: service.listInboxItems(decodeURIComponent(params.agentId), {
739
+ afterItemId: query.after_item_id,
740
+ includeAcked: query.include_acked ? query.include_acked === "true" : undefined,
741
+ }),
742
+ };
743
+ });
744
+ app.get("/agents/:agentId/inbox/watch", {
745
+ schema: {
746
+ tags: ["inbox"],
747
+ params: {
748
+ type: "object",
749
+ required: ["agentId"],
750
+ properties: {
751
+ agentId: { type: "string", minLength: 1 },
752
+ },
753
+ },
754
+ querystring: {
755
+ type: "object",
756
+ additionalProperties: false,
757
+ properties: {
758
+ after_item_id: { type: "string" },
759
+ include_acked: { type: "string", enum: ["true", "false"] },
760
+ heartbeat_ms: { type: "string", pattern: "^[1-9][0-9]*$" },
761
+ },
762
+ },
763
+ response: {
764
+ 200: { type: "string" },
765
+ },
766
+ },
767
+ }, async (request, reply) => {
768
+ const params = request.params;
769
+ const query = request.query;
770
+ const agentId = decodeURIComponent(params.agentId);
771
+ const watchOptions = {
772
+ afterItemId: query.after_item_id,
773
+ includeAcked: query.include_acked ? query.include_acked === "true" : undefined,
774
+ heartbeatMs: query.heartbeat_ms ? Number(query.heartbeat_ms) : undefined,
775
+ };
776
+ reply.hijack();
777
+ const raw = reply.raw;
778
+ raw.writeHead(200, {
779
+ "content-type": "text/event-stream; charset=utf-8",
780
+ "cache-control": "no-cache, no-transform",
781
+ connection: "keep-alive",
782
+ });
783
+ const session = service.watchInbox(agentId, watchOptions, (event) => {
784
+ sendSse(raw, event.event, event);
785
+ });
786
+ sendSse(raw, "items", {
787
+ event: "items",
788
+ agentId,
789
+ items: session.initialItems,
790
+ });
791
+ session.start();
792
+ const heartbeatMs = watchOptions.heartbeatMs ?? 15_000;
793
+ const heartbeat = setInterval(() => {
794
+ sendSse(raw, "heartbeat", {
795
+ event: "heartbeat",
796
+ agentId,
797
+ timestamp: new Date().toISOString(),
798
+ });
799
+ }, heartbeatMs);
800
+ const cleanup = () => {
801
+ clearInterval(heartbeat);
802
+ session.close();
803
+ raw.end();
804
+ };
805
+ request.raw.on("close", cleanup);
806
+ request.raw.on("error", cleanup);
807
+ });
808
+ app.post("/agents/:agentId/inbox/compact", {
809
+ schema: {
810
+ tags: ["inbox"],
811
+ params: {
812
+ type: "object",
813
+ required: ["agentId"],
814
+ properties: {
815
+ agentId: { type: "string", minLength: 1 },
816
+ },
817
+ },
818
+ response: {
819
+ 200: jsonObjectSchema,
820
+ },
821
+ },
822
+ }, async (request) => {
823
+ const params = request.params;
824
+ return service.compactInbox(decodeURIComponent(params.agentId));
825
+ });
826
+ app.post("/agents/:agentId/inbox/ack", {
827
+ schema: {
828
+ tags: ["inbox"],
829
+ params: {
830
+ type: "object",
831
+ required: ["agentId"],
832
+ properties: {
833
+ agentId: { type: "string", minLength: 1 },
834
+ },
835
+ },
836
+ body: {
837
+ oneOf: [
838
+ {
839
+ type: "object",
840
+ additionalProperties: false,
841
+ required: ["itemIds"],
842
+ properties: {
843
+ itemIds: {
844
+ type: "array",
845
+ minItems: 1,
846
+ items: { type: "string", minLength: 1 },
847
+ },
848
+ },
849
+ },
850
+ {
851
+ type: "object",
852
+ additionalProperties: false,
853
+ required: ["throughItemId"],
854
+ properties: {
855
+ throughItemId: { type: "string", minLength: 1 },
856
+ },
857
+ },
858
+ {
859
+ type: "object",
860
+ additionalProperties: false,
861
+ required: ["all"],
862
+ properties: {
863
+ all: { type: "boolean", const: true },
864
+ },
865
+ },
866
+ ],
867
+ },
868
+ response: {
869
+ 200: jsonObjectSchema,
870
+ 400: errorResponseSchema,
871
+ },
872
+ },
873
+ }, async (request) => {
874
+ const params = request.params;
875
+ const body = request.body;
876
+ return service.ackInbox(decodeURIComponent(params.agentId), {
877
+ itemIds: body.itemIds ?? [],
878
+ throughItemId: body.throughItemId ?? null,
879
+ all: body.all ?? false,
880
+ });
881
+ });
882
+ app.post("/deliveries/send", {
883
+ schema: {
884
+ tags: ["deliveries"],
885
+ body: {
886
+ oneOf: [
887
+ {
888
+ type: "object",
889
+ additionalProperties: false,
890
+ required: ["kind", "payload", "deliveryHandle"],
891
+ properties: {
892
+ kind: { type: "string", minLength: 1 },
893
+ payload: jsonObjectSchema,
894
+ deliveryHandle: deliveryHandleSchema,
895
+ },
896
+ },
897
+ {
898
+ type: "object",
899
+ additionalProperties: false,
900
+ required: ["kind", "payload", "provider", "surface", "targetRef"],
901
+ properties: {
902
+ kind: { type: "string", minLength: 1 },
903
+ payload: jsonObjectSchema,
904
+ provider: { type: "string", minLength: 1 },
905
+ surface: { type: "string", minLength: 1 },
906
+ targetRef: { type: "string", minLength: 1 },
907
+ threadRef: { type: "string" },
908
+ replyMode: { type: "string" },
909
+ },
910
+ },
911
+ ],
912
+ },
913
+ response: {
914
+ 200: jsonObjectSchema,
915
+ 400: errorResponseSchema,
916
+ },
917
+ },
918
+ }, async (request) => service.sendDelivery(request.body));
919
+ app.setNotFoundHandler((_request, reply) => {
920
+ void reply.code(404).send({ error: "not found" });
921
+ });
922
+ app.setErrorHandler((error, _request, reply) => {
923
+ const message = error instanceof Error ? error.message : String(error);
924
+ if (error.validation) {
925
+ void reply.code(400).send({ error: normalizeValidationMessage(message) });
926
+ return;
927
+ }
928
+ if (message.startsWith("unknown ")) {
929
+ void reply.code(404).send({ error: message });
930
+ return;
931
+ }
932
+ if (isBadRequestError(message)) {
933
+ void reply.code(400).send({ error: message });
934
+ return;
326
935
  }
936
+ void reply.code(500).send({ error: message });
327
937
  });
938
+ return app;
328
939
  }
329
- function parseRequiredString(value, message) {
330
- const parsed = parseOptionalString(value);
331
- if (!parsed) {
332
- throw new Error(message);
333
- }
334
- return parsed;
940
+ function createServer(service) {
941
+ const app = buildFastifyServer(service);
942
+ const ready = app.ready();
943
+ const server = node_http_1.default.createServer((req, res) => {
944
+ void Promise.resolve(ready)
945
+ .then(() => {
946
+ app.routing(req, res);
947
+ })
948
+ .catch((error) => {
949
+ res.statusCode = 500;
950
+ res.setHeader("content-type", "application/json; charset=utf-8");
951
+ const message = error instanceof Error ? error.message : String(error);
952
+ res.end((0, util_1.jsonResponse)({ error: message }));
953
+ });
954
+ });
955
+ const originalClose = server.close.bind(server);
956
+ const wrappedClose = ((callback) => {
957
+ return originalClose((closeError) => {
958
+ void app
959
+ .close()
960
+ .then(() => callback?.(closeError))
961
+ .catch((appCloseError) => {
962
+ const normalized = appCloseError instanceof Error
963
+ ? appCloseError
964
+ : new Error(String(appCloseError));
965
+ callback?.(closeError ?? normalized);
966
+ });
967
+ });
968
+ });
969
+ server.close = wrappedClose;
970
+ return server;
335
971
  }
336
- function parseOptionalString(value) {
972
+ function optionalString(value) {
337
973
  if (typeof value !== "string") {
338
974
  return undefined;
339
975
  }
340
976
  const trimmed = value.trim();
341
977
  return trimmed.length > 0 ? trimmed : undefined;
342
978
  }
343
- function parseOptionalInteger(value) {
344
- if (value == null) {
345
- return undefined;
979
+ function normalizeValidationMessage(message) {
980
+ if (message.includes("must be boolean")) {
981
+ return "expected boolean";
346
982
  }
347
- const parsed = Number(value);
348
- if (!Number.isInteger(parsed)) {
349
- throw new Error(`expected integer, received ${String(value)}`);
983
+ if (message.includes("must be integer")) {
984
+ return "expected integer";
350
985
  }
351
- return parsed;
352
- }
353
- function parseOptionalBoolean(value) {
354
- if (value == null) {
355
- return undefined;
356
- }
357
- if (typeof value !== "boolean") {
358
- throw new Error(`expected boolean, received ${String(value)}`);
359
- }
360
- return value;
361
- }
362
- function parsePositiveInteger(value) {
363
- const parsed = parseOptionalInteger(value);
364
- if (parsed == null || parsed <= 0) {
365
- throw new Error(`expected positive integer, received ${String(value)}`);
986
+ if (message.includes("must be object")) {
987
+ return "expected object";
366
988
  }
367
- return parsed;
989
+ return message;
368
990
  }
369
991
  function isBadRequestError(message) {
370
992
  return (message.startsWith("manual append is not supported") ||
371
- message.startsWith("fixtures/emit requires") ||
372
993
  message.startsWith("sources/events requires") ||
994
+ message.startsWith("source remove requires") ||
373
995
  message.startsWith("deliveryHandle requires") ||
374
996
  message.startsWith("subscriptions/reset requires") ||
375
- message.startsWith("agents/register requires") ||
997
+ message.startsWith("agents requires") ||
376
998
  message.startsWith("agents/targets requires") ||
999
+ message.startsWith("subscriptions requires") ||
377
1000
  message.startsWith("agent register conflict") ||
378
1001
  message.startsWith("unsupported activation target kind") ||
379
1002
  message.startsWith("unsupported lifecycle mode") ||
380
1003
  message.startsWith("unsupported start policy") ||
381
1004
  message.startsWith("unsupported terminal") ||
382
- message.startsWith("source type is reserved and not yet supported") ||
383
1005
  message.startsWith("expected boolean") ||
384
1006
  message.startsWith("expected integer") ||
385
1007
  message.startsWith("expected positive integer") ||
386
1008
  message.startsWith("notifyLeaseMs must be a positive integer") ||
387
1009
  message.startsWith("invalid webhook activation target") ||
1010
+ message.startsWith("remote_source requires") ||
1011
+ message.startsWith("remote_source profile") ||
388
1012
  message.includes("requires tmuxPaneId") ||
389
1013
  message.includes("requires iTerm2 session identity") ||
390
1014
  message.includes("requires a supported terminal context") ||
391
1015
  message.includes("belongs to agent"));
392
1016
  }
393
- function parseOptionalDeliveryHandle(value) {
394
- if (!value) {
395
- return null;
396
- }
397
- const object = (0, util_1.asObject)(value);
398
- const provider = parseRequiredString(object.provider, "deliveryHandle.provider is required");
399
- const surface = parseRequiredString(object.surface, "deliveryHandle.surface is required");
400
- const targetRef = parseRequiredString(object.targetRef, "deliveryHandle.targetRef is required");
401
- return {
402
- provider,
403
- surface,
404
- targetRef,
405
- threadRef: parseOptionalString(object.threadRef) ?? null,
406
- replyMode: parseOptionalString(object.replyMode) ?? null,
407
- };
408
- }