@voicethere/agent 0.3.0 → 0.5.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.
@@ -3,6 +3,7 @@ const require = createRequire(import.meta.url);
3
3
 
4
4
 
5
5
  // dist/runtime.js
6
+ import { randomUUID } from "node:crypto";
6
7
  import { AsyncLocalStorage } from "node:async_hooks";
7
8
 
8
9
  // dist/session-serial-queue.js
@@ -129,11 +130,58 @@ var SessionSerialQueue = class {
129
130
  var SESSION_START_INIT_DELAY_ENABLED_ENV = "AGENT_SESSION_START_INIT_DELAY_ENABLED";
130
131
  var SESSION_START_INIT_DELAY_MS_ENV = "AGENT_SESSION_START_INIT_DELAY_MS";
131
132
  var DEFAULT_SESSION_START_INIT_DELAY_MS = 500;
133
+ function isRecordingControlAckMessage(value) {
134
+ if (!value || typeof value !== "object")
135
+ return false;
136
+ const msg = value;
137
+ return msg.type === "recording_control_ack" && typeof msg.requestId === "string";
138
+ }
139
+ function isWebhookMessage(value) {
140
+ if (!value || typeof value !== "object")
141
+ return false;
142
+ const msg = value;
143
+ return msg.type === "webhook" && typeof msg.eventId === "string" && typeof msg.projectId === "string";
144
+ }
145
+ function coerceInboundBinary(value) {
146
+ if (!value)
147
+ return null;
148
+ if (Buffer.isBuffer(value))
149
+ return value;
150
+ if (value instanceof Uint8Array) {
151
+ return Buffer.from(value.buffer, value.byteOffset, value.byteLength);
152
+ }
153
+ if (value instanceof ArrayBuffer)
154
+ return Buffer.from(value);
155
+ if (ArrayBuffer.isView(value)) {
156
+ return Buffer.from(value.buffer, value.byteOffset, value.byteLength);
157
+ }
158
+ if (typeof value === "object") {
159
+ const maybeBufferLike = value;
160
+ if (maybeBufferLike.type === "Buffer" && Array.isArray(maybeBufferLike.data)) {
161
+ return Buffer.from(maybeBufferLike.data);
162
+ }
163
+ }
164
+ return null;
165
+ }
166
+ function normalizeWebhookHeaders(value) {
167
+ if (!value || typeof value !== "object")
168
+ return {};
169
+ const out = {};
170
+ for (const [key, raw] of Object.entries(value)) {
171
+ if (typeof raw === "string") {
172
+ out[key] = raw;
173
+ }
174
+ }
175
+ return out;
176
+ }
132
177
  function isParentMessage(value) {
133
178
  if (!value || typeof value !== "object")
134
179
  return false;
135
180
  const msg = value;
136
- return msg.type === "session_start" || msg.type === "speech_event" || msg.type === "session_end" || msg.type === "data_channel_message" || msg.type === "data_channel_binary" || msg.type === "idle_timeout";
181
+ return msg.type === "session_start" || msg.type === "speech_event" || msg.type === "session_end" || msg.type === "data_channel_message" || msg.type === "data_channel_binary" || msg.type === "idle_timeout" || msg.type === "recording_control_ack" || msg.type === "webhook";
182
+ }
183
+ function isSessionScopedParentMessage(value) {
184
+ return isParentMessage(value) && !isWebhookMessage(value);
137
185
  }
138
186
  function parseDataChannelPayload(raw) {
139
187
  try {
@@ -144,6 +192,28 @@ function parseDataChannelPayload(raw) {
144
192
  }
145
193
  var peerEnvBySessionId = /* @__PURE__ */ new Map();
146
194
  var endedSessionIds = /* @__PURE__ */ new Set();
195
+ var pendingRecordingAcks = /* @__PURE__ */ new Map();
196
+ function handleRecordingControlAck(message) {
197
+ const pending = pendingRecordingAcks.get(message.requestId);
198
+ if (!pending)
199
+ return;
200
+ clearTimeout(pending.timer);
201
+ pendingRecordingAcks.delete(message.requestId);
202
+ pending.resolve({
203
+ ok: message.ok,
204
+ reason: message.reason,
205
+ requestId: message.requestId
206
+ });
207
+ }
208
+ function clearPendingRecordingAcksForSession(sessionId, reason) {
209
+ for (const [requestId, pending] of pendingRecordingAcks) {
210
+ if (pending.sessionId !== sessionId)
211
+ continue;
212
+ clearTimeout(pending.timer);
213
+ pendingRecordingAcks.delete(requestId);
214
+ pending.resolve({ ok: false, reason, requestId });
215
+ }
216
+ }
147
217
  var sessionExecutionContext = new AsyncLocalStorage();
148
218
  var agentLogSessionContext = new AsyncLocalStorage();
149
219
  var inboundQueueAuthority = null;
@@ -221,6 +291,49 @@ function resolveSessionStartInitDelayMs() {
221
291
  return 0;
222
292
  return parseNonNegativeIntegerEnv(process.env[SESSION_START_INIT_DELAY_MS_ENV], DEFAULT_SESSION_START_INIT_DELAY_MS);
223
293
  }
294
+ async function handleWebhookMessage(message, handlers) {
295
+ if (!handlers.onWebhook)
296
+ return;
297
+ const body = coerceInboundBinary(message.body);
298
+ if (!body) {
299
+ agentLog("warn", "webhook ipc dropped: body is not binary");
300
+ return;
301
+ }
302
+ const ctx = {
303
+ eventId: message.eventId,
304
+ projectId: message.projectId,
305
+ method: typeof message.method === "string" ? message.method : "POST",
306
+ path: typeof message.path === "string" ? message.path : "",
307
+ headers: normalizeWebhookHeaders(message.headers),
308
+ body,
309
+ contentType: typeof message.contentType === "string" ? message.contentType : null,
310
+ receivedAt: typeof message.receivedAt === "string" ? message.receivedAt : ""
311
+ };
312
+ try {
313
+ const started = Date.now();
314
+ await handlers.onWebhook(ctx);
315
+ sendParentMessage({
316
+ type: "webhook_handled",
317
+ projectId: message.projectId,
318
+ eventId: message.eventId,
319
+ durationMs: Date.now() - started
320
+ });
321
+ } catch (error) {
322
+ const err = error instanceof Error ? error : new Error(String(error));
323
+ await runErrorHook(handlers, {
324
+ sessionId: "",
325
+ projectId: message.projectId,
326
+ env: process.env,
327
+ error: err
328
+ });
329
+ sendParentMessage({
330
+ type: "agent_error",
331
+ sessionId: "",
332
+ message: err.message,
333
+ stack: err.stack
334
+ });
335
+ }
336
+ }
224
337
  async function handleParentMessage(message, handlers) {
225
338
  switch (message.type) {
226
339
  case "session_start":
@@ -232,7 +345,8 @@ async function handleParentMessage(message, handlers) {
232
345
  }
233
346
  await (handlers.onClientJoin ?? handlers.onSessionStart)?.({
234
347
  sessionId: message.sessionId,
235
- env: message.env
348
+ env: message.env,
349
+ recordingAvailable: message.recordingAvailable ?? false
236
350
  });
237
351
  sendParentMessage({
238
352
  type: "session_start_ack",
@@ -267,6 +381,7 @@ async function handleParentMessage(message, handlers) {
267
381
  });
268
382
  break;
269
383
  case "session_end":
384
+ clearPendingRecordingAcksForSession(message.sessionId, "session_ended");
270
385
  peerEnvBySessionId.delete(message.sessionId);
271
386
  await (handlers.onClientLeave ?? handlers.onSessionEnd)?.({
272
387
  sessionId: message.sessionId
@@ -283,10 +398,19 @@ function defineAgent(handlers) {
283
398
  inboundQueueAuthority = inboundBySession;
284
399
  const agentStartReady = runAgentStartHook(handlers);
285
400
  process.on("message", (message) => {
286
- if (!isParentMessage(message))
401
+ if (isRecordingControlAckMessage(message)) {
402
+ handleRecordingControlAck(message);
403
+ return;
404
+ }
405
+ if (isWebhookMessage(message)) {
406
+ void agentStartReady.then(() => handleWebhookMessage(message, handlers));
407
+ return;
408
+ }
409
+ if (!isSessionScopedParentMessage(message))
287
410
  return;
288
411
  if (message.type === "session_end") {
289
412
  endedSessionIds.add(message.sessionId);
413
+ clearPendingRecordingAcksForSession(message.sessionId, "session_ended");
290
414
  inboundBySession.clear(message.sessionId);
291
415
  }
292
416
  if (message.type === "session_start") {
@@ -3,6 +3,7 @@ const require = createRequire(import.meta.url);
3
3
 
4
4
 
5
5
  // dist/runtime.js
6
+ import { randomUUID } from "node:crypto";
6
7
  import { AsyncLocalStorage } from "node:async_hooks";
7
8
 
8
9
  // dist/session-serial-queue.js
@@ -129,11 +130,58 @@ var SessionSerialQueue = class {
129
130
  var SESSION_START_INIT_DELAY_ENABLED_ENV = "AGENT_SESSION_START_INIT_DELAY_ENABLED";
130
131
  var SESSION_START_INIT_DELAY_MS_ENV = "AGENT_SESSION_START_INIT_DELAY_MS";
131
132
  var DEFAULT_SESSION_START_INIT_DELAY_MS = 500;
133
+ function isRecordingControlAckMessage(value) {
134
+ if (!value || typeof value !== "object")
135
+ return false;
136
+ const msg = value;
137
+ return msg.type === "recording_control_ack" && typeof msg.requestId === "string";
138
+ }
139
+ function isWebhookMessage(value) {
140
+ if (!value || typeof value !== "object")
141
+ return false;
142
+ const msg = value;
143
+ return msg.type === "webhook" && typeof msg.eventId === "string" && typeof msg.projectId === "string";
144
+ }
145
+ function coerceInboundBinary(value) {
146
+ if (!value)
147
+ return null;
148
+ if (Buffer.isBuffer(value))
149
+ return value;
150
+ if (value instanceof Uint8Array) {
151
+ return Buffer.from(value.buffer, value.byteOffset, value.byteLength);
152
+ }
153
+ if (value instanceof ArrayBuffer)
154
+ return Buffer.from(value);
155
+ if (ArrayBuffer.isView(value)) {
156
+ return Buffer.from(value.buffer, value.byteOffset, value.byteLength);
157
+ }
158
+ if (typeof value === "object") {
159
+ const maybeBufferLike = value;
160
+ if (maybeBufferLike.type === "Buffer" && Array.isArray(maybeBufferLike.data)) {
161
+ return Buffer.from(maybeBufferLike.data);
162
+ }
163
+ }
164
+ return null;
165
+ }
166
+ function normalizeWebhookHeaders(value) {
167
+ if (!value || typeof value !== "object")
168
+ return {};
169
+ const out = {};
170
+ for (const [key, raw] of Object.entries(value)) {
171
+ if (typeof raw === "string") {
172
+ out[key] = raw;
173
+ }
174
+ }
175
+ return out;
176
+ }
132
177
  function isParentMessage(value) {
133
178
  if (!value || typeof value !== "object")
134
179
  return false;
135
180
  const msg = value;
136
- return msg.type === "session_start" || msg.type === "speech_event" || msg.type === "session_end" || msg.type === "data_channel_message" || msg.type === "data_channel_binary" || msg.type === "idle_timeout";
181
+ return msg.type === "session_start" || msg.type === "speech_event" || msg.type === "session_end" || msg.type === "data_channel_message" || msg.type === "data_channel_binary" || msg.type === "idle_timeout" || msg.type === "recording_control_ack" || msg.type === "webhook";
182
+ }
183
+ function isSessionScopedParentMessage(value) {
184
+ return isParentMessage(value) && !isWebhookMessage(value);
137
185
  }
138
186
  function parseDataChannelPayload(raw) {
139
187
  try {
@@ -144,6 +192,28 @@ function parseDataChannelPayload(raw) {
144
192
  }
145
193
  var peerEnvBySessionId = /* @__PURE__ */ new Map();
146
194
  var endedSessionIds = /* @__PURE__ */ new Set();
195
+ var pendingRecordingAcks = /* @__PURE__ */ new Map();
196
+ function handleRecordingControlAck(message) {
197
+ const pending = pendingRecordingAcks.get(message.requestId);
198
+ if (!pending)
199
+ return;
200
+ clearTimeout(pending.timer);
201
+ pendingRecordingAcks.delete(message.requestId);
202
+ pending.resolve({
203
+ ok: message.ok,
204
+ reason: message.reason,
205
+ requestId: message.requestId
206
+ });
207
+ }
208
+ function clearPendingRecordingAcksForSession(sessionId, reason) {
209
+ for (const [requestId, pending] of pendingRecordingAcks) {
210
+ if (pending.sessionId !== sessionId)
211
+ continue;
212
+ clearTimeout(pending.timer);
213
+ pendingRecordingAcks.delete(requestId);
214
+ pending.resolve({ ok: false, reason, requestId });
215
+ }
216
+ }
147
217
  var sessionExecutionContext = new AsyncLocalStorage();
148
218
  var agentLogSessionContext = new AsyncLocalStorage();
149
219
  var inboundQueueAuthority = null;
@@ -221,6 +291,49 @@ function resolveSessionStartInitDelayMs() {
221
291
  return 0;
222
292
  return parseNonNegativeIntegerEnv(process.env[SESSION_START_INIT_DELAY_MS_ENV], DEFAULT_SESSION_START_INIT_DELAY_MS);
223
293
  }
294
+ async function handleWebhookMessage(message, handlers) {
295
+ if (!handlers.onWebhook)
296
+ return;
297
+ const body = coerceInboundBinary(message.body);
298
+ if (!body) {
299
+ agentLog("warn", "webhook ipc dropped: body is not binary");
300
+ return;
301
+ }
302
+ const ctx = {
303
+ eventId: message.eventId,
304
+ projectId: message.projectId,
305
+ method: typeof message.method === "string" ? message.method : "POST",
306
+ path: typeof message.path === "string" ? message.path : "",
307
+ headers: normalizeWebhookHeaders(message.headers),
308
+ body,
309
+ contentType: typeof message.contentType === "string" ? message.contentType : null,
310
+ receivedAt: typeof message.receivedAt === "string" ? message.receivedAt : ""
311
+ };
312
+ try {
313
+ const started = Date.now();
314
+ await handlers.onWebhook(ctx);
315
+ sendParentMessage({
316
+ type: "webhook_handled",
317
+ projectId: message.projectId,
318
+ eventId: message.eventId,
319
+ durationMs: Date.now() - started
320
+ });
321
+ } catch (error) {
322
+ const err = error instanceof Error ? error : new Error(String(error));
323
+ await runErrorHook(handlers, {
324
+ sessionId: "",
325
+ projectId: message.projectId,
326
+ env: process.env,
327
+ error: err
328
+ });
329
+ sendParentMessage({
330
+ type: "agent_error",
331
+ sessionId: "",
332
+ message: err.message,
333
+ stack: err.stack
334
+ });
335
+ }
336
+ }
224
337
  async function handleParentMessage(message, handlers) {
225
338
  switch (message.type) {
226
339
  case "session_start":
@@ -232,7 +345,8 @@ async function handleParentMessage(message, handlers) {
232
345
  }
233
346
  await (handlers.onClientJoin ?? handlers.onSessionStart)?.({
234
347
  sessionId: message.sessionId,
235
- env: message.env
348
+ env: message.env,
349
+ recordingAvailable: message.recordingAvailable ?? false
236
350
  });
237
351
  sendParentMessage({
238
352
  type: "session_start_ack",
@@ -267,6 +381,7 @@ async function handleParentMessage(message, handlers) {
267
381
  });
268
382
  break;
269
383
  case "session_end":
384
+ clearPendingRecordingAcksForSession(message.sessionId, "session_ended");
270
385
  peerEnvBySessionId.delete(message.sessionId);
271
386
  await (handlers.onClientLeave ?? handlers.onSessionEnd)?.({
272
387
  sessionId: message.sessionId
@@ -283,10 +398,19 @@ function defineAgent(handlers) {
283
398
  inboundQueueAuthority = inboundBySession;
284
399
  const agentStartReady = runAgentStartHook(handlers);
285
400
  process.on("message", (message) => {
286
- if (!isParentMessage(message))
401
+ if (isRecordingControlAckMessage(message)) {
402
+ handleRecordingControlAck(message);
403
+ return;
404
+ }
405
+ if (isWebhookMessage(message)) {
406
+ void agentStartReady.then(() => handleWebhookMessage(message, handlers));
407
+ return;
408
+ }
409
+ if (!isSessionScopedParentMessage(message))
287
410
  return;
288
411
  if (message.type === "session_end") {
289
412
  endedSessionIds.add(message.sessionId);
413
+ clearPendingRecordingAcksForSession(message.sessionId, "session_ended");
290
414
  inboundBySession.clear(message.sessionId);
291
415
  }
292
416
  if (message.type === "session_start") {
@@ -1 +1 @@
1
- {"version":3,"file":"registry.d.ts","sourceRoot":"","sources":["../../src/templates/registry.ts"],"names":[],"mappings":"AAAA,MAAM,MAAM,YAAY,GAAG,SAAS,GAAG,KAAK,CAAC;AAE7C,MAAM,WAAW,uBAAuB;IACtC,EAAE,EAAE,MAAM,CAAC;IACX,6EAA6E;IAC7E,KAAK,EAAE,MAAM,CAAC;IACd,IAAI,EAAE,YAAY,CAAC;IACnB,mFAAmF;IACnF,YAAY,EAAE,OAAO,CAAC;IACtB,WAAW,EAAE,MAAM,CAAC;IACpB,yEAAyE;IACzE,WAAW,EAAE,MAAM,EAAE,CAAC;CACvB;AAED,eAAO,MAAM,eAAe,EAAE,SAAS,uBAAuB,EAyEpD,CAAC;AAEX,MAAM,MAAM,eAAe,GAAG,CAAC,OAAO,eAAe,CAAC,CAAC,MAAM,CAAC,CAAC,IAAI,CAAC,CAAC;AAMrE,wBAAgB,iBAAiB,CAAC,EAAE,EAAE,MAAM,GAAG,EAAE,IAAI,eAAe,CAEnE;AAED,wBAAgB,eAAe,CAAC,EAAE,EAAE,MAAM,GAAG,uBAAuB,CAMnE;AAED,wBAAgB,yBAAyB,IAAI,uBAAuB,EAAE,CAErE"}
1
+ {"version":3,"file":"registry.d.ts","sourceRoot":"","sources":["../../src/templates/registry.ts"],"names":[],"mappings":"AAAA,MAAM,MAAM,YAAY,GAAG,SAAS,GAAG,KAAK,CAAC;AAE7C,MAAM,WAAW,uBAAuB;IACtC,EAAE,EAAE,MAAM,CAAC;IACX,6EAA6E;IAC7E,KAAK,EAAE,MAAM,CAAC;IACd,IAAI,EAAE,YAAY,CAAC;IACnB,mFAAmF;IACnF,YAAY,EAAE,OAAO,CAAC;IACtB,WAAW,EAAE,MAAM,CAAC;IACpB,yEAAyE;IACzE,WAAW,EAAE,MAAM,EAAE,CAAC;CACvB;AAED,eAAO,MAAM,eAAe,EAAE,SAAS,uBAAuB,EA2FpD,CAAC;AAEX,MAAM,MAAM,eAAe,GAAG,CAAC,OAAO,eAAe,CAAC,CAAC,MAAM,CAAC,CAAC,IAAI,CAAC,CAAC;AAMrE,wBAAgB,iBAAiB,CAAC,EAAE,EAAE,MAAM,GAAG,EAAE,IAAI,eAAe,CAEnE;AAED,wBAAgB,eAAe,CAAC,EAAE,EAAE,MAAM,GAAG,uBAAuB,CAMnE;AAED,wBAAgB,yBAAyB,IAAI,uBAAuB,EAAE,CAErE"}
@@ -31,6 +31,22 @@ export const AGENT_TEMPLATES = [
31
31
  description: "Authoritative multi-object sync sample for real-time games and simulations.",
32
32
  sourceFiles: ["game-sync.ts"],
33
33
  },
34
+ {
35
+ id: "webhooks",
36
+ entry: "webhooks.ts",
37
+ kind: "product",
38
+ seedOnCreate: true,
39
+ description: "Inbound webhook handler — HMAC verify on raw body, then DataChannel + speak fan-out.",
40
+ sourceFiles: ["webhooks.ts"],
41
+ },
42
+ {
43
+ id: "webhooks-redis",
44
+ entry: "webhooks-redis.ts",
45
+ kind: "product",
46
+ seedOnCreate: true,
47
+ description: "Webhook handler with Redis atomic shared counter plus DataChannel fan-out.",
48
+ sourceFiles: ["webhooks-redis.ts"],
49
+ },
34
50
  {
35
51
  id: "echo-smoke",
36
52
  entry: "echo-smoke.ts",
@@ -1 +1 @@
1
- {"version":3,"file":"registry.js","sourceRoot":"","sources":["../../src/templates/registry.ts"],"names":[],"mappings":"AAcA,MAAM,CAAC,MAAM,eAAe,GAAuC;IACjE;QACE,EAAE,EAAE,MAAM;QACV,KAAK,EAAE,SAAS;QAChB,IAAI,EAAE,SAAS;QACf,YAAY,EAAE,IAAI;QAClB,WAAW,EACT,8EAA8E;QAChF,WAAW,EAAE,CAAC,SAAS,CAAC;KACzB;IACD;QACE,EAAE,EAAE,SAAS;QACb,KAAK,EAAE,YAAY;QACnB,IAAI,EAAE,SAAS;QACf,YAAY,EAAE,IAAI;QAClB,WAAW,EACT,qEAAqE;QACvE,WAAW,EAAE,CAAC,YAAY,CAAC;KAC5B;IACD;QACE,EAAE,EAAE,eAAe;QACnB,KAAK,EAAE,UAAU;QACjB,IAAI,EAAE,SAAS;QACf,YAAY,EAAE,IAAI;QAClB,WAAW,EACT,uFAAuF;QACzF,WAAW,EAAE,CAAC,UAAU,CAAC;KAC1B;IACD;QACE,EAAE,EAAE,WAAW;QACf,KAAK,EAAE,cAAc;QACrB,IAAI,EAAE,SAAS;QACf,YAAY,EAAE,IAAI;QAClB,WAAW,EACT,6EAA6E;QAC/E,WAAW,EAAE,CAAC,cAAc,CAAC;KAC9B;IACD;QACE,EAAE,EAAE,YAAY;QAChB,KAAK,EAAE,eAAe;QACtB,IAAI,EAAE,KAAK;QACX,YAAY,EAAE,KAAK;QACnB,WAAW,EACT,6EAA6E;QAC/E,WAAW,EAAE,CAAC,eAAe,CAAC;KAC/B;IACD;QACE,EAAE,EAAE,OAAO;QACX,KAAK,EAAE,UAAU;QACjB,IAAI,EAAE,KAAK;QACX,YAAY,EAAE,KAAK;QACnB,WAAW,EACT,4EAA4E;QAC9E,WAAW,EAAE,CAAC,UAAU,CAAC;KAC1B;IACD;QACE,EAAE,EAAE,iBAAiB;QACrB,KAAK,EAAE,oBAAoB;QAC3B,IAAI,EAAE,KAAK;QACX,YAAY,EAAE,KAAK;QACnB,WAAW,EACT,6EAA6E;QAC/E,WAAW,EAAE,CAAC,oBAAoB,CAAC;KACpC;IACD;QACE,EAAE,EAAE,YAAY;QAChB,KAAK,EAAE,qBAAqB;QAC5B,IAAI,EAAE,KAAK;QACX,YAAY,EAAE,KAAK;QACnB,WAAW,EACT,sFAAsF;QACxF,WAAW,EAAE,CAAC,qBAAqB,EAAE,4BAA4B,CAAC;KACnE;CACO,CAAC;AAIX,MAAM,cAAc,GAAG,IAAI,GAAG,CAC5B,eAAe,CAAC,GAAG,CAAC,CAAC,QAAQ,EAAE,EAAE,CAAC,CAAC,QAAQ,CAAC,EAAE,EAAE,QAAQ,CAAC,CAAC,CAC3D,CAAC;AAEF,MAAM,UAAU,iBAAiB,CAAC,EAAU;IAC1C,OAAO,cAAc,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC;AAChC,CAAC;AAED,MAAM,UAAU,eAAe,CAAC,EAAU;IACxC,MAAM,QAAQ,GAAG,cAAc,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC;IACxC,IAAI,CAAC,QAAQ,EAAE,CAAC;QACd,MAAM,IAAI,KAAK,CAAC,8BAA8B,EAAE,EAAE,CAAC,CAAC;IACtD,CAAC;IACD,OAAO,QAAQ,CAAC;AAClB,CAAC;AAED,MAAM,UAAU,yBAAyB;IACvC,OAAO,eAAe,CAAC,MAAM,CAAC,CAAC,QAAQ,EAAE,EAAE,CAAC,QAAQ,CAAC,YAAY,CAAC,CAAC;AACrE,CAAC"}
1
+ {"version":3,"file":"registry.js","sourceRoot":"","sources":["../../src/templates/registry.ts"],"names":[],"mappings":"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"}
@@ -3,6 +3,7 @@ const require = createRequire(import.meta.url);
3
3
 
4
4
 
5
5
  // dist/runtime.js
6
+ import { randomUUID } from "node:crypto";
6
7
  import { AsyncLocalStorage } from "node:async_hooks";
7
8
 
8
9
  // dist/session-serial-queue.js
@@ -129,11 +130,58 @@ var SessionSerialQueue = class {
129
130
  var SESSION_START_INIT_DELAY_ENABLED_ENV = "AGENT_SESSION_START_INIT_DELAY_ENABLED";
130
131
  var SESSION_START_INIT_DELAY_MS_ENV = "AGENT_SESSION_START_INIT_DELAY_MS";
131
132
  var DEFAULT_SESSION_START_INIT_DELAY_MS = 500;
133
+ function isRecordingControlAckMessage(value) {
134
+ if (!value || typeof value !== "object")
135
+ return false;
136
+ const msg = value;
137
+ return msg.type === "recording_control_ack" && typeof msg.requestId === "string";
138
+ }
139
+ function isWebhookMessage(value) {
140
+ if (!value || typeof value !== "object")
141
+ return false;
142
+ const msg = value;
143
+ return msg.type === "webhook" && typeof msg.eventId === "string" && typeof msg.projectId === "string";
144
+ }
145
+ function coerceInboundBinary(value) {
146
+ if (!value)
147
+ return null;
148
+ if (Buffer.isBuffer(value))
149
+ return value;
150
+ if (value instanceof Uint8Array) {
151
+ return Buffer.from(value.buffer, value.byteOffset, value.byteLength);
152
+ }
153
+ if (value instanceof ArrayBuffer)
154
+ return Buffer.from(value);
155
+ if (ArrayBuffer.isView(value)) {
156
+ return Buffer.from(value.buffer, value.byteOffset, value.byteLength);
157
+ }
158
+ if (typeof value === "object") {
159
+ const maybeBufferLike = value;
160
+ if (maybeBufferLike.type === "Buffer" && Array.isArray(maybeBufferLike.data)) {
161
+ return Buffer.from(maybeBufferLike.data);
162
+ }
163
+ }
164
+ return null;
165
+ }
166
+ function normalizeWebhookHeaders(value) {
167
+ if (!value || typeof value !== "object")
168
+ return {};
169
+ const out = {};
170
+ for (const [key, raw] of Object.entries(value)) {
171
+ if (typeof raw === "string") {
172
+ out[key] = raw;
173
+ }
174
+ }
175
+ return out;
176
+ }
132
177
  function isParentMessage(value) {
133
178
  if (!value || typeof value !== "object")
134
179
  return false;
135
180
  const msg = value;
136
- return msg.type === "session_start" || msg.type === "speech_event" || msg.type === "session_end" || msg.type === "data_channel_message" || msg.type === "data_channel_binary" || msg.type === "idle_timeout";
181
+ return msg.type === "session_start" || msg.type === "speech_event" || msg.type === "session_end" || msg.type === "data_channel_message" || msg.type === "data_channel_binary" || msg.type === "idle_timeout" || msg.type === "recording_control_ack" || msg.type === "webhook";
182
+ }
183
+ function isSessionScopedParentMessage(value) {
184
+ return isParentMessage(value) && !isWebhookMessage(value);
137
185
  }
138
186
  function parseDataChannelPayload(raw) {
139
187
  try {
@@ -144,6 +192,28 @@ function parseDataChannelPayload(raw) {
144
192
  }
145
193
  var peerEnvBySessionId = /* @__PURE__ */ new Map();
146
194
  var endedSessionIds = /* @__PURE__ */ new Set();
195
+ var pendingRecordingAcks = /* @__PURE__ */ new Map();
196
+ function handleRecordingControlAck(message) {
197
+ const pending = pendingRecordingAcks.get(message.requestId);
198
+ if (!pending)
199
+ return;
200
+ clearTimeout(pending.timer);
201
+ pendingRecordingAcks.delete(message.requestId);
202
+ pending.resolve({
203
+ ok: message.ok,
204
+ reason: message.reason,
205
+ requestId: message.requestId
206
+ });
207
+ }
208
+ function clearPendingRecordingAcksForSession(sessionId, reason) {
209
+ for (const [requestId, pending] of pendingRecordingAcks) {
210
+ if (pending.sessionId !== sessionId)
211
+ continue;
212
+ clearTimeout(pending.timer);
213
+ pendingRecordingAcks.delete(requestId);
214
+ pending.resolve({ ok: false, reason, requestId });
215
+ }
216
+ }
147
217
  var sessionExecutionContext = new AsyncLocalStorage();
148
218
  var agentLogSessionContext = new AsyncLocalStorage();
149
219
  var inboundQueueAuthority = null;
@@ -221,6 +291,49 @@ function resolveSessionStartInitDelayMs() {
221
291
  return 0;
222
292
  return parseNonNegativeIntegerEnv(process.env[SESSION_START_INIT_DELAY_MS_ENV], DEFAULT_SESSION_START_INIT_DELAY_MS);
223
293
  }
294
+ async function handleWebhookMessage(message, handlers) {
295
+ if (!handlers.onWebhook)
296
+ return;
297
+ const body = coerceInboundBinary(message.body);
298
+ if (!body) {
299
+ agentLog("warn", "webhook ipc dropped: body is not binary");
300
+ return;
301
+ }
302
+ const ctx = {
303
+ eventId: message.eventId,
304
+ projectId: message.projectId,
305
+ method: typeof message.method === "string" ? message.method : "POST",
306
+ path: typeof message.path === "string" ? message.path : "",
307
+ headers: normalizeWebhookHeaders(message.headers),
308
+ body,
309
+ contentType: typeof message.contentType === "string" ? message.contentType : null,
310
+ receivedAt: typeof message.receivedAt === "string" ? message.receivedAt : ""
311
+ };
312
+ try {
313
+ const started = Date.now();
314
+ await handlers.onWebhook(ctx);
315
+ sendParentMessage({
316
+ type: "webhook_handled",
317
+ projectId: message.projectId,
318
+ eventId: message.eventId,
319
+ durationMs: Date.now() - started
320
+ });
321
+ } catch (error) {
322
+ const err = error instanceof Error ? error : new Error(String(error));
323
+ await runErrorHook(handlers, {
324
+ sessionId: "",
325
+ projectId: message.projectId,
326
+ env: process.env,
327
+ error: err
328
+ });
329
+ sendParentMessage({
330
+ type: "agent_error",
331
+ sessionId: "",
332
+ message: err.message,
333
+ stack: err.stack
334
+ });
335
+ }
336
+ }
224
337
  async function handleParentMessage(message, handlers) {
225
338
  switch (message.type) {
226
339
  case "session_start":
@@ -232,7 +345,8 @@ async function handleParentMessage(message, handlers) {
232
345
  }
233
346
  await (handlers.onClientJoin ?? handlers.onSessionStart)?.({
234
347
  sessionId: message.sessionId,
235
- env: message.env
348
+ env: message.env,
349
+ recordingAvailable: message.recordingAvailable ?? false
236
350
  });
237
351
  sendParentMessage({
238
352
  type: "session_start_ack",
@@ -267,6 +381,7 @@ async function handleParentMessage(message, handlers) {
267
381
  });
268
382
  break;
269
383
  case "session_end":
384
+ clearPendingRecordingAcksForSession(message.sessionId, "session_ended");
270
385
  peerEnvBySessionId.delete(message.sessionId);
271
386
  await (handlers.onClientLeave ?? handlers.onSessionEnd)?.({
272
387
  sessionId: message.sessionId
@@ -283,10 +398,19 @@ function defineAgent(handlers) {
283
398
  inboundQueueAuthority = inboundBySession;
284
399
  const agentStartReady = runAgentStartHook(handlers);
285
400
  process.on("message", (message) => {
286
- if (!isParentMessage(message))
401
+ if (isRecordingControlAckMessage(message)) {
402
+ handleRecordingControlAck(message);
403
+ return;
404
+ }
405
+ if (isWebhookMessage(message)) {
406
+ void agentStartReady.then(() => handleWebhookMessage(message, handlers));
407
+ return;
408
+ }
409
+ if (!isSessionScopedParentMessage(message))
287
410
  return;
288
411
  if (message.type === "session_end") {
289
412
  endedSessionIds.add(message.sessionId);
413
+ clearPendingRecordingAcksForSession(message.sessionId, "session_ended");
290
414
  inboundBySession.clear(message.sessionId);
291
415
  }
292
416
  if (message.type === "session_start") {