@townco/agent 0.1.74 → 0.1.76

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.
@@ -609,12 +609,10 @@ export function makeHttpTransport(agent, agentDir, agentName) {
609
609
  });
610
610
  const port = Number.parseInt(process.env.PORT || "3100", 10);
611
611
  logger.info("Starting HTTP server", { port });
612
- Bun.serve({
613
- fetch: app.fetch,
614
- port,
615
- });
612
+ const hostname = Bun.env.BIND_HOST || "localhost";
613
+ Bun.serve({ fetch: app.fetch, hostname, port });
616
614
  logger.info("HTTP server listening", {
617
- url: `http://localhost:${port}`,
615
+ url: `http://${hostname}:${port}`,
618
616
  port,
619
617
  });
620
618
  }
@@ -863,7 +863,9 @@ export class LangchainAgent {
863
863
  toolUseBlock.name &&
864
864
  !preliminaryToolCallIds.has(toolUseBlock.id)) {
865
865
  preliminaryToolCallIds.add(toolUseBlock.id);
866
- pendingToolCallNotifications.push({
866
+ // Yield the preliminary notification immediately (don't buffer)
867
+ // This allows the UI to show the "selecting" state before params arrive
868
+ yield {
867
869
  sessionUpdate: "tool_call",
868
870
  toolCallId: toolUseBlock.id,
869
871
  title: toolUseBlock.name,
@@ -871,7 +873,7 @@ export class LangchainAgent {
871
873
  status: "pending",
872
874
  rawInput: {}, // Args not available yet
873
875
  _meta: { messageId: req.messageId },
874
- });
876
+ };
875
877
  }
876
878
  }
877
879
  else if (part.type === "input_json_delta") {
@@ -881,8 +883,8 @@ export class LangchainAgent {
881
883
  throw new Error(`Unhandled AIMessageChunk content block type: ${part.type}\n${JSON.stringify(part)}`);
882
884
  }
883
885
  }
884
- // Don't flush here - these are preliminary tool_use blocks
885
- // We'll flush when we get the full tool calls in "updates" mode
886
+ // Preliminary tool_use blocks are yielded immediately above
887
+ // Full tool calls with params will come later in "updates" mode
886
888
  }
887
889
  else {
888
890
  throw new Error(`Unhandled AIMessageChunk content type: ${typeof aiMessage.content}`);
@@ -327,95 +327,105 @@ async function querySubagent(agentName, agentPath, agentWorkingDirectory, query)
327
327
  // Map of tool call IDs to their indices in toolCalls array
328
328
  const toolCallMap = new Map();
329
329
  const ssePromise = (async () => {
330
- const sseResponse = await fetch(`${baseUrl}/events`, {
331
- headers: { "X-Session-ID": sessionId },
332
- signal: sseAbortController.signal,
333
- });
334
- if (!sseResponse.ok || !sseResponse.body) {
335
- throw new Error(`SSE connection failed: HTTP ${sseResponse.status}`);
336
- }
337
- const reader = sseResponse.body.getReader();
338
- const decoder = new TextDecoder();
339
- let buffer = "";
340
- while (true) {
341
- const { done, value } = await reader.read();
342
- if (done)
343
- break;
344
- buffer += decoder.decode(value, { stream: true });
345
- const lines = buffer.split("\n");
346
- buffer = lines.pop() || "";
347
- for (const line of lines) {
348
- if (line.startsWith("data:")) {
349
- const data = line.substring(5).trim();
350
- if (!data)
351
- continue;
352
- try {
353
- const message = JSON.parse(data);
354
- const update = message.params?.update;
355
- if (message.method !== "session/update" || !update)
330
+ try {
331
+ const sseResponse = await fetch(`${baseUrl}/events`, {
332
+ headers: { "X-Session-ID": sessionId },
333
+ signal: sseAbortController.signal,
334
+ });
335
+ if (!sseResponse.ok || !sseResponse.body) {
336
+ throw new Error(`SSE connection failed: HTTP ${sseResponse.status}`);
337
+ }
338
+ const reader = sseResponse.body.getReader();
339
+ const decoder = new TextDecoder();
340
+ let buffer = "";
341
+ while (true) {
342
+ const { done, value } = await reader.read();
343
+ if (done)
344
+ break;
345
+ buffer += decoder.decode(value, { stream: true });
346
+ const lines = buffer.split("\n");
347
+ buffer = lines.pop() || "";
348
+ for (const line of lines) {
349
+ if (line.startsWith("data:")) {
350
+ const data = line.substring(5).trim();
351
+ if (!data)
356
352
  continue;
357
- // Handle agent_message_chunk - accumulate text
358
- if (update.sessionUpdate === "agent_message_chunk") {
359
- const content = update.content;
360
- if (content?.type === "text" &&
361
- typeof content.text === "string") {
362
- responseText += content.text;
363
- currentMessage.content += content.text;
364
- // Add to contentBlocks - append to last text block or create new one
365
- const lastBlock = currentMessage.contentBlocks[currentMessage.contentBlocks.length - 1];
366
- if (lastBlock && lastBlock.type === "text") {
367
- lastBlock.text += content.text;
368
- }
369
- else {
370
- currentMessage.contentBlocks.push({
371
- type: "text",
372
- text: content.text,
373
- });
353
+ try {
354
+ const message = JSON.parse(data);
355
+ const update = message.params?.update;
356
+ if (message.method !== "session/update" || !update)
357
+ continue;
358
+ // Handle agent_message_chunk - accumulate text
359
+ if (update.sessionUpdate === "agent_message_chunk") {
360
+ const content = update.content;
361
+ if (content?.type === "text" &&
362
+ typeof content.text === "string") {
363
+ responseText += content.text;
364
+ currentMessage.content += content.text;
365
+ // Add to contentBlocks - append to last text block or create new one
366
+ const lastBlock = currentMessage.contentBlocks[currentMessage.contentBlocks.length - 1];
367
+ if (lastBlock && lastBlock.type === "text") {
368
+ lastBlock.text += content.text;
369
+ }
370
+ else {
371
+ currentMessage.contentBlocks.push({
372
+ type: "text",
373
+ text: content.text,
374
+ });
375
+ }
374
376
  }
375
377
  }
376
- }
377
- // Handle tool_call - track new tool calls
378
- if (update.sessionUpdate === "tool_call" && update.toolCallId) {
379
- const toolCall = {
380
- id: update.toolCallId,
381
- title: update.title || "Tool call",
382
- prettyName: update._meta?.prettyName,
383
- icon: update._meta?.icon,
384
- status: update.status || "pending",
385
- };
386
- currentMessage.toolCalls.push(toolCall);
387
- toolCallMap.set(update.toolCallId, currentMessage.toolCalls.length - 1);
388
- // Add to contentBlocks for interleaved display
389
- currentMessage.contentBlocks.push({
390
- type: "tool_call",
391
- toolCall,
392
- });
393
- }
394
- // Handle tool_call_update - update existing tool call status
395
- if (update.sessionUpdate === "tool_call_update" &&
396
- update.toolCallId) {
397
- const idx = toolCallMap.get(update.toolCallId);
398
- if (idx !== undefined && currentMessage.toolCalls[idx]) {
399
- if (update.status) {
400
- currentMessage.toolCalls[idx].status =
401
- update.status;
402
- }
403
- // Also update in contentBlocks
404
- const block = currentMessage.contentBlocks.find((b) => b.type === "tool_call" &&
405
- b.toolCall.id === update.toolCallId);
406
- if (block && update.status) {
407
- block.toolCall.status =
408
- update.status;
378
+ // Handle tool_call - track new tool calls
379
+ if (update.sessionUpdate === "tool_call" && update.toolCallId) {
380
+ const toolCall = {
381
+ id: update.toolCallId,
382
+ title: update.title || "Tool call",
383
+ prettyName: update._meta?.prettyName,
384
+ icon: update._meta?.icon,
385
+ status: update.status ||
386
+ "pending",
387
+ };
388
+ currentMessage.toolCalls.push(toolCall);
389
+ toolCallMap.set(update.toolCallId, currentMessage.toolCalls.length - 1);
390
+ // Add to contentBlocks for interleaved display
391
+ currentMessage.contentBlocks.push({
392
+ type: "tool_call",
393
+ toolCall,
394
+ });
395
+ }
396
+ // Handle tool_call_update - update existing tool call status
397
+ if (update.sessionUpdate === "tool_call_update" &&
398
+ update.toolCallId) {
399
+ const idx = toolCallMap.get(update.toolCallId);
400
+ if (idx !== undefined && currentMessage.toolCalls[idx]) {
401
+ if (update.status) {
402
+ currentMessage.toolCalls[idx].status =
403
+ update.status;
404
+ }
405
+ // Also update in contentBlocks
406
+ const block = currentMessage.contentBlocks.find((b) => b.type === "tool_call" &&
407
+ b.toolCall.id === update.toolCallId);
408
+ if (block && update.status) {
409
+ block.toolCall.status =
410
+ update.status;
411
+ }
409
412
  }
410
413
  }
411
414
  }
412
- }
413
- catch {
414
- // Ignore malformed SSE data
415
+ catch {
416
+ // Ignore malformed SSE data
417
+ }
415
418
  }
416
419
  }
417
420
  }
418
421
  }
422
+ catch (error) {
423
+ // Ignore AbortError - this is expected when we abort the SSE connection
424
+ if (error instanceof DOMException && error.name === "AbortError") {
425
+ return;
426
+ }
427
+ throw error;
428
+ }
419
429
  })();
420
430
  // Step 4: Send the prompt with timeout
421
431
  const timeoutMs = 5 * 60 * 1000; // 5 minutes