@townco/agent 0.1.63 → 0.1.70

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.
@@ -569,7 +569,8 @@ export class AgentAcpAdapter {
569
569
  const context_size = calculateContextSize(contextMessages, this.agent.definition.systemPrompt ?? undefined, undefined, // No LLM-reported tokens yet
570
570
  this.currentToolOverheadTokens, // Include tool overhead
571
571
  this.currentMcpOverheadTokens);
572
- const contextSnapshot = createContextSnapshot(session.messages.length, new Date().toISOString(), previousContext, context_size);
572
+ const contextSnapshot = createContextSnapshot(session.messages.length - 1, // Exclude the newly added user message (it will be passed separately via prompt)
573
+ new Date().toISOString(), previousContext, context_size);
573
574
  session.context.push(contextSnapshot);
574
575
  await this.saveSessionToDisk(params.sessionId, session);
575
576
  }
@@ -608,6 +609,13 @@ export class AgentAcpAdapter {
608
609
  sessionId: params.sessionId,
609
610
  contextMessageCount: contextMessages.length,
610
611
  totalSessionMessages: session.messages.length,
612
+ contextMessages: contextMessages.map((m) => ({
613
+ role: m.role,
614
+ content: JSON.stringify(m.content).slice(0, 100),
615
+ })),
616
+ promptContent: params.prompt
617
+ .map((p) => p.type === "text" ? p.text.slice(0, 100) : `[${p.type}]`)
618
+ .join(" "),
611
619
  latestContextEntry: session.context.length > 0 &&
612
620
  session.context[session.context.length - 1]
613
621
  ? {
@@ -553,6 +553,7 @@ export function makeHttpTransport(agent, agentDir, agentName) {
553
553
  logger.info("Starting HTTP server", { port });
554
554
  Bun.serve({
555
555
  fetch: app.fetch,
556
+ hostname: Bun.env.BIND_HOST || "localhost",
556
557
  port,
557
558
  });
558
559
  logger.info("HTTP server listening", {
@@ -82,6 +82,20 @@ export class LangchainAgent {
82
82
  const countedMessageIds = new Set();
83
83
  // Track tool calls for which we've emitted preliminary notifications (from early tool_use blocks)
84
84
  const preliminaryToolCallIds = new Set();
85
+ // Buffer tool call notifications until content streaming completes
86
+ const pendingToolCallNotifications = [];
87
+ // Helper to flush all buffered tool calls (called when we detect text streaming is done)
88
+ function* flushPendingToolCalls() {
89
+ if (pendingToolCallNotifications.length === 0) {
90
+ return;
91
+ }
92
+ // Yield all buffered notifications
93
+ for (const notification of pendingToolCallNotifications) {
94
+ yield notification;
95
+ }
96
+ // Clear the buffer after flushing
97
+ pendingToolCallNotifications.length = 0;
98
+ }
85
99
  // Set session_id as a base attribute so all spans in this invocation include it
86
100
  telemetry.setBaseAttributes({
87
101
  "agent.session_id": req.sessionId,
@@ -629,8 +643,8 @@ export class LangchainAgent {
629
643
  // Check if we already emitted a preliminary notification from early tool_use block
630
644
  const alreadyEmittedPreliminary = preliminaryToolCallIds.has(toolCall.id);
631
645
  if (alreadyEmittedPreliminary) {
632
- // Update the existing preliminary notification with full details
633
- yield {
646
+ // Buffer the update notification
647
+ pendingToolCallNotifications.push({
634
648
  sessionUpdate: "tool_call_update",
635
649
  toolCallId: toolCall.id,
636
650
  title: toolCall.name,
@@ -642,11 +656,11 @@ export class LangchainAgent {
642
656
  ...(icon ? { icon } : {}),
643
657
  ...(batchId ? { batchId } : {}),
644
658
  },
645
- };
659
+ });
646
660
  }
647
661
  else {
648
- // Emit full tool_call notification (fallback for non-streaming scenarios)
649
- yield {
662
+ // Buffer full tool_call notification
663
+ pendingToolCallNotifications.push({
650
664
  sessionUpdate: "tool_call",
651
665
  toolCallId: toolCall.id,
652
666
  title: toolCall.name,
@@ -660,16 +674,21 @@ export class LangchainAgent {
660
674
  ...(icon ? { icon } : {}),
661
675
  ...(batchId ? { batchId } : {}),
662
676
  },
663
- };
677
+ });
664
678
  }
665
- // Always emit in_progress status update
666
- yield {
679
+ // Buffer in_progress status update
680
+ pendingToolCallNotifications.push({
667
681
  sessionUpdate: "tool_call_update",
668
682
  toolCallId: toolCall.id,
669
683
  status: "in_progress",
670
684
  ...(tokenUsage ? { tokenUsage } : {}),
671
685
  _meta: { messageId: req.messageId },
672
- };
686
+ });
687
+ }
688
+ // After processing all tool calls in this chunk, flush them
689
+ // (tool calls indicate text streaming is complete)
690
+ if (toolCalls.length > 0) {
691
+ yield* flushPendingToolCalls();
673
692
  }
674
693
  }
675
694
  }
@@ -776,14 +795,14 @@ export class LangchainAgent {
776
795
  yield msgToYield;
777
796
  }
778
797
  else if (part.type === "tool_use") {
779
- // Emit early notification for tool use as soon as we detect it
798
+ // Buffer early notification for tool use
780
799
  // The tool_use block contains { type, id, name, input }
781
800
  const toolUseBlock = part;
782
801
  if (toolUseBlock.id &&
783
802
  toolUseBlock.name &&
784
803
  !preliminaryToolCallIds.has(toolUseBlock.id)) {
785
804
  preliminaryToolCallIds.add(toolUseBlock.id);
786
- yield {
805
+ pendingToolCallNotifications.push({
787
806
  sessionUpdate: "tool_call",
788
807
  toolCallId: toolUseBlock.id,
789
808
  title: toolUseBlock.name,
@@ -791,7 +810,7 @@ export class LangchainAgent {
791
810
  status: "pending",
792
811
  rawInput: {}, // Args not available yet
793
812
  _meta: { messageId: req.messageId },
794
- };
813
+ });
795
814
  }
796
815
  }
797
816
  else if (part.type === "input_json_delta") {
@@ -801,6 +820,8 @@ export class LangchainAgent {
801
820
  throw new Error(`Unhandled AIMessageChunk content block type: ${part.type}\n${JSON.stringify(part)}`);
802
821
  }
803
822
  }
823
+ // Don't flush here - these are preliminary tool_use blocks
824
+ // We'll flush when we get the full tool calls in "updates" mode
804
825
  }
805
826
  else {
806
827
  throw new Error(`Unhandled AIMessageChunk content type: ${typeof aiMessage.content}`);
@@ -822,16 +843,16 @@ export class LangchainAgent {
822
843
  toolCallId: aiMessage.tool_call_id,
823
844
  ...(isError ? { error: aiMessage.content } : {}),
824
845
  });
825
- // Send status update (metadata only, no content)
826
- yield {
846
+ // Buffer status update (metadata only, no content)
847
+ pendingToolCallNotifications.push({
827
848
  sessionUpdate: "tool_call_update",
828
849
  toolCallId: aiMessage.tool_call_id,
829
850
  status,
830
851
  ...(isError ? { error: aiMessage.content } : {}),
831
852
  _meta: { messageId: req.messageId },
832
- };
833
- // Send tool output separately (via direct SSE, bypassing PostgreSQL NOTIFY)
834
- yield {
853
+ });
854
+ // Buffer tool output separately
855
+ pendingToolCallNotifications.push({
835
856
  sessionUpdate: "tool_output",
836
857
  toolCallId: aiMessage.tool_call_id,
837
858
  content: [
@@ -845,7 +866,9 @@ export class LangchainAgent {
845
866
  ],
846
867
  rawOutput: { content: aiMessage.content },
847
868
  _meta: { messageId: req.messageId },
848
- };
869
+ });
870
+ // Flush tool outputs after buffering
871
+ yield* flushPendingToolCalls();
849
872
  }
850
873
  else {
851
874
  throw new Error(`Unhandled ToolMessage content type: ${typeof aiMessage.content}`);
@@ -863,6 +886,8 @@ export class LangchainAgent {
863
886
  }
864
887
  // Yield any remaining pending subagent connection updates after stream ends
865
888
  yield* yieldPendingSubagentUpdates();
889
+ // Now that content streaming is complete, yield all buffered tool call notifications
890
+ yield* flushPendingToolCalls();
866
891
  // Clean up subagent connection listener
867
892
  subagentEvents.off("connection", onSubagentConnection);
868
893
  // Cancel any pending wait