botholomew 0.15.3 → 0.15.5

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.
@@ -241,7 +241,7 @@ export const TaskPanel = memo(function TaskPanel({
241
241
  deleteConfirm.pressDelete(t.name || t.id);
242
242
  return;
243
243
  }
244
- if (input === "r") {
244
+ if (key.ctrl && (input === "r" || input === "R")) {
245
245
  forceRefresh();
246
246
  return;
247
247
  }
@@ -376,7 +376,7 @@ export const TaskPanel = memo(function TaskPanel({
376
376
  <Text dimColor>
377
377
  {focus === "detail"
378
378
  ? "↑↓ scroll · ⇧↑↓ page · g/G top/bot · ← back to list"
379
- : "↑↓ select · → enter detail · f filter · p priority · d delete (×2) · r refresh"}
379
+ : "↑↓ select · → enter detail · f filter · p priority · d delete (×2) · ^R refresh"}
380
380
  </Text>
381
381
  </Box>
382
382
  </Box>
@@ -394,7 +394,7 @@ export const ThreadPanel = memo(function ThreadPanel({
394
394
  deleteConfirm.pressDelete(t.title || "(untitled)");
395
395
  return;
396
396
  }
397
- if (input === "r") {
397
+ if (key.ctrl && (input === "r" || input === "R")) {
398
398
  forceRefresh();
399
399
  return;
400
400
  }
@@ -575,7 +575,7 @@ export const ThreadPanel = memo(function ThreadPanel({
575
575
  <Text dimColor>
576
576
  {focus === "detail"
577
577
  ? "↑↓ scroll · ⇧↑↓ page · g/G top/bot · ← back to list"
578
- : `↑↓ select · → enter detail · s search · f filter · d delete (×2)${selectedThread && !selectedThread.ended_at ? " · w follow" : ""} · r refresh`}
578
+ : `↑↓ select · → enter detail · s search · f filter · d delete (×2)${selectedThread && !selectedThread.ended_at ? " · w follow" : ""} · ^R refresh`}
579
579
  </Text>
580
580
  </Box>
581
581
  </Box>
@@ -40,6 +40,8 @@ export interface ToolCallData {
40
40
  timestamp: Date;
41
41
  largeResult?: LargeResultMeta;
42
42
  isError?: boolean;
43
+ /** Side-effect notes emitted by the tool (e.g. "Created subtask: …"). */
44
+ notes?: string[];
43
45
  }
44
46
 
45
47
  interface ToolCallProps {
@@ -106,6 +108,18 @@ export function ToolCall({ tool }: ToolCallProps) {
106
108
  {tool.largeResult.pages}pg]
107
109
  </Text>
108
110
  )}
111
+ {tool.notes?.map((note, i) => (
112
+ <Text
113
+ // biome-ignore lint/suspicious/noArrayIndexKey: notes are append-only
114
+ key={i}
115
+ color={theme.accent}
116
+ dimColor
117
+ wrap="truncate-end"
118
+ >
119
+ {" ℹ "}
120
+ {note}
121
+ </Text>
122
+ ))}
109
123
  </Box>
110
124
  );
111
125
  }
@@ -1,6 +1,6 @@
1
1
  import { basename } from "node:path";
2
2
  import { Box, Text, useInput, useStdout } from "ink";
3
- import { memo, useEffect, useMemo, useState } from "react";
3
+ import { memo, useCallback, useEffect, useMemo, useState } from "react";
4
4
  import { readLogTail } from "../../worker/log-reader.ts";
5
5
  import {
6
6
  deleteWorkerLog,
@@ -70,6 +70,7 @@ export const WorkerPanel = memo(function WorkerPanel({
70
70
  const [workers, setWorkers] = useState<Worker[]>([]);
71
71
  const [selectedIndex, setSelectedIndex] = useState(0);
72
72
  const [filterIdx, setFilterIdx] = useState(0);
73
+ const [refreshTick, setRefreshTick] = useState(0);
73
74
  const [now, setNow] = useState(() => new Date());
74
75
  const [viewMode, setViewMode] = useState<"detail" | "log">("detail");
75
76
  const [logContent, setLogContent] = useState("");
@@ -79,6 +80,7 @@ export const WorkerPanel = memo(function WorkerPanel({
79
80
  const [logFollow, setLogFollow] = useState(true);
80
81
  const [focus, setFocus] = useState<FocusState>("list");
81
82
 
83
+ // biome-ignore lint/correctness/useExhaustiveDependencies: refreshTick triggers manual refresh
82
84
  useEffect(() => {
83
85
  let mounted = true;
84
86
 
@@ -104,7 +106,7 @@ export const WorkerPanel = memo(function WorkerPanel({
104
106
  mounted = false;
105
107
  clearInterval(interval);
106
108
  };
107
- }, [projectDir, filterIdx]);
109
+ }, [projectDir, filterIdx, refreshTick]);
108
110
 
109
111
  const selected = workers[selectedIndex];
110
112
  const selectedLogPath = selected?.log_path ?? null;
@@ -171,6 +173,10 @@ export const WorkerPanel = memo(function WorkerPanel({
171
173
  const viewModeRef = useLatestRef(viewMode);
172
174
  const selectedLogPathRef = useLatestRef(selectedLogPath);
173
175
 
176
+ const forceRefresh = useCallback(() => {
177
+ setRefreshTick((t) => t + 1);
178
+ }, []);
179
+
174
180
  const deleteConfirm = useDeleteConfirm(() => {
175
181
  const path = selectedLogPathRef.current;
176
182
  if (!path) return;
@@ -238,6 +244,11 @@ export const WorkerPanel = memo(function WorkerPanel({
238
244
  deleteConfirm.pressDelete(`worker log: ${basename(path)}`);
239
245
  return;
240
246
  }
247
+
248
+ if (key.ctrl && (input === "r" || input === "R")) {
249
+ forceRefresh();
250
+ return;
251
+ }
241
252
  },
242
253
  { isActive },
243
254
  );
@@ -257,8 +268,8 @@ export const WorkerPanel = memo(function WorkerPanel({
257
268
  {focus === "detail"
258
269
  ? " · ↑↓ scroll ⇧↑↓ page g/G top/bot ← back to list l toggle"
259
270
  : viewMode === "log"
260
- ? " · ↑↓ select → enter log l detail f filter d delete log (×2)"
261
- : " · ↑↓ select → enter detail l view log f filter"}
271
+ ? " · ↑↓ select → enter log l detail f filter d delete log (×2) ^R refresh"
272
+ : " · ↑↓ select → enter detail l view log f filter ^R refresh"}
262
273
  </Text>
263
274
  </Box>
264
275
  <DeleteArmedBanner
@@ -1,64 +0,0 @@
1
- import { join } from "node:path";
2
- import { z } from "zod";
3
- import { getPromptsDir } from "../../constants.ts";
4
- import {
5
- type ContextFileMeta,
6
- parseContextFile,
7
- serializeContextFile,
8
- } from "../../utils/frontmatter.ts";
9
- import type { ToolDefinition } from "../tool.ts";
10
-
11
- const inputSchema = z.object({
12
- content: z
13
- .string()
14
- .describe(
15
- "The new beliefs content (replaces existing body, frontmatter is preserved)",
16
- ),
17
- });
18
-
19
- const outputSchema = z.object({
20
- message: z.string(),
21
- path: z.string(),
22
- is_error: z.boolean(),
23
- });
24
-
25
- export const updateBeliefsTool = {
26
- name: "update_beliefs",
27
- description:
28
- "Update the agent's beliefs file (prompts/beliefs.md). Preserves frontmatter, replaces content body.",
29
- group: "context",
30
- inputSchema,
31
- outputSchema,
32
- execute: async (input, ctx) => {
33
- const filePath = join(getPromptsDir(ctx.projectDir), "beliefs.md");
34
- const file = Bun.file(filePath);
35
-
36
- let meta: ContextFileMeta = {
37
- loading: "always",
38
- "agent-modification": true,
39
- };
40
-
41
- if (await file.exists()) {
42
- const raw = await file.text();
43
- const parsed = parseContextFile(raw);
44
- meta = parsed.meta;
45
-
46
- if (!meta["agent-modification"]) {
47
- return {
48
- message: "Agent modification not allowed for this file",
49
- path: filePath,
50
- is_error: true,
51
- };
52
- }
53
- }
54
-
55
- const serialized = serializeContextFile(meta, input.content);
56
- await Bun.write(filePath, serialized);
57
-
58
- return {
59
- message: "Updated beliefs.md",
60
- path: filePath,
61
- is_error: false,
62
- };
63
- },
64
- } satisfies ToolDefinition<typeof inputSchema, typeof outputSchema>;
@@ -1,64 +0,0 @@
1
- import { join } from "node:path";
2
- import { z } from "zod";
3
- import { getPromptsDir } from "../../constants.ts";
4
- import {
5
- type ContextFileMeta,
6
- parseContextFile,
7
- serializeContextFile,
8
- } from "../../utils/frontmatter.ts";
9
- import type { ToolDefinition } from "../tool.ts";
10
-
11
- const inputSchema = z.object({
12
- content: z
13
- .string()
14
- .describe(
15
- "The new goals content (replaces existing body, frontmatter is preserved)",
16
- ),
17
- });
18
-
19
- const outputSchema = z.object({
20
- message: z.string(),
21
- path: z.string(),
22
- is_error: z.boolean(),
23
- });
24
-
25
- export const updateGoalsTool = {
26
- name: "update_goals",
27
- description:
28
- "Update the agent's goals file (prompts/goals.md). Preserves frontmatter, replaces content body.",
29
- group: "context",
30
- inputSchema,
31
- outputSchema,
32
- execute: async (input, ctx) => {
33
- const filePath = join(getPromptsDir(ctx.projectDir), "goals.md");
34
- const file = Bun.file(filePath);
35
-
36
- let meta: ContextFileMeta = {
37
- loading: "always",
38
- "agent-modification": true,
39
- };
40
-
41
- if (await file.exists()) {
42
- const raw = await file.text();
43
- const parsed = parseContextFile(raw);
44
- meta = parsed.meta;
45
-
46
- if (!meta["agent-modification"]) {
47
- return {
48
- message: "Agent modification not allowed for this file",
49
- path: filePath,
50
- is_error: true,
51
- };
52
- }
53
- }
54
-
55
- const serialized = serializeContextFile(meta, input.content);
56
- await Bun.write(filePath, serialized);
57
-
58
- return {
59
- message: "Updated goals.md",
60
- path: filePath,
61
- is_error: false,
62
- };
63
- },
64
- } satisfies ToolDefinition<typeof inputSchema, typeof outputSchema>;