botholomew 0.9.8 → 0.9.9

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "botholomew",
3
- "version": "0.9.8",
3
+ "version": "0.9.9",
4
4
  "description": "An autonomous AI agent for knowledge work — works your task queue while you sleep.",
5
5
  "type": "module",
6
6
  "bin": {
package/src/chat/agent.ts CHANGED
@@ -160,6 +160,7 @@ export interface ToolEndMeta {
160
160
 
161
161
  export interface ChatTurnCallbacks {
162
162
  onToken: (text: string) => void;
163
+ onToolPreparing?: (id: string, name: string) => void;
163
164
  onToolStart: (id: string, name: string, input: string) => void;
164
165
  onToolEnd: (
165
166
  id: string,
@@ -251,6 +252,18 @@ export async function runChatTurn(input: {
251
252
  callbacks.onToken(text);
252
253
  });
253
254
 
255
+ stream.on("streamEvent", (event) => {
256
+ if (
257
+ event.type === "content_block_start" &&
258
+ event.content_block.type === "tool_use"
259
+ ) {
260
+ callbacks.onToolPreparing?.(
261
+ event.content_block.id,
262
+ event.content_block.name,
263
+ );
264
+ }
265
+ });
266
+
254
267
  stream.on("contentBlock", (block) => {
255
268
  if (block.type === "tool_use") {
256
269
  earlyReportedToolIds.add(block.id);
package/src/tui/App.tsx CHANGED
@@ -133,6 +133,10 @@ export function App({
133
133
  const [isLoading, setIsLoading] = useState(false);
134
134
  const [streamingText, setStreamingText] = useState("");
135
135
  const [activeToolCalls, setActiveToolCalls] = useState<ToolCallData[]>([]);
136
+ const [preparingTool, setPreparingTool] = useState<{
137
+ id: string;
138
+ name: string;
139
+ } | null>(null);
136
140
  const [ready, setReady] = useState(false);
137
141
  const skipSplash = !!(resumeThreadId || initialPrompt);
138
142
  const [splashDone, setSplashDone] = useState(skipSplash);
@@ -329,6 +333,7 @@ export function App({
329
333
  setIsLoading(true);
330
334
  setStreamingText("");
331
335
  setActiveToolCalls([]);
336
+ setPreparingTool(null);
332
337
 
333
338
  const userMsg: ChatMessage = {
334
339
  id: msgId(),
@@ -370,6 +375,9 @@ export function App({
370
375
  lastStreamFlush = now;
371
376
  }
372
377
  },
378
+ onToolPreparing: (id, name) => {
379
+ setPreparingTool({ id, name });
380
+ },
373
381
  onToolStart: (id, name, input) => {
374
382
  if (currentText) {
375
383
  finalizeSegment();
@@ -383,6 +391,7 @@ export function App({
383
391
  };
384
392
  pendingToolCalls.push(tc);
385
393
  setActiveToolCalls([...pendingToolCalls]);
394
+ setPreparingTool(null);
386
395
  },
387
396
  onToolEnd: (id, _name, output, isError, meta) => {
388
397
  const tc = pendingToolCalls.find((t) => t.id === id);
@@ -410,6 +419,7 @@ export function App({
410
419
  } finally {
411
420
  setStreamingText("");
412
421
  setActiveToolCalls([]);
422
+ setPreparingTool(null);
413
423
  }
414
424
  }
415
425
 
@@ -700,6 +710,7 @@ export function App({
700
710
  streamingText={streamingText}
701
711
  isLoading={isLoading}
702
712
  activeToolCalls={activeToolCalls}
713
+ preparingTool={preparingTool}
703
714
  />
704
715
  </Box>
705
716
  <Box
@@ -17,6 +17,7 @@ interface MessageListProps {
17
17
  streamingText: string;
18
18
  isLoading: boolean;
19
19
  activeToolCalls: ToolCallData[];
20
+ preparingTool: { id: string; name: string } | null;
20
21
  }
21
22
 
22
23
  function formatTime(date: Date): string {
@@ -127,6 +128,7 @@ export function MessageList({
127
128
  streamingText,
128
129
  isLoading,
129
130
  activeToolCalls,
131
+ preparingTool,
130
132
  }: MessageListProps) {
131
133
  return (
132
134
  <>
@@ -160,7 +162,17 @@ export function MessageList({
160
162
  </Box>
161
163
  )}
162
164
 
165
+ {preparingTool && (
166
+ <Box marginTop={1}>
167
+ <Text color={theme.accent}>
168
+ <Spinner type="dots" />
169
+ </Text>
170
+ <Text dimColor> Preparing tool call: {preparingTool.name}...</Text>
171
+ </Box>
172
+ )}
173
+
163
174
  {isLoading &&
175
+ !preparingTool &&
164
176
  !streamingText &&
165
177
  (activeToolCalls.length === 0 ||
166
178
  activeToolCalls.every((tc) => !tc.running)) && (
@@ -144,9 +144,22 @@ class FakeMessageStream extends EventEmitter {
144
144
  if (delay > 0) await new Promise((r) => setTimeout(r, delay));
145
145
  }
146
146
  const final = buildFinalMessage(text, this.turn.toolCalls);
147
+ let blockIndex = text ? 1 : 0;
147
148
  for (const block of final.content) {
148
149
  if ((block as { type: string }).type === "tool_use") {
149
- this.emit("contentBlock", block as ToolUseBlock);
150
+ const toolUse = block as ToolUseBlock;
151
+ this.emit("streamEvent", {
152
+ type: "content_block_start",
153
+ index: blockIndex,
154
+ content_block: {
155
+ type: "tool_use",
156
+ id: toolUse.id,
157
+ name: toolUse.name,
158
+ input: {},
159
+ },
160
+ });
161
+ this.emit("contentBlock", toolUse);
162
+ blockIndex++;
150
163
  }
151
164
  }
152
165
  this.resolveFinal(final);