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.
- package/package.json +1 -1
- package/src/chat/agent.ts +18 -5
- package/src/commands/chat.ts +2 -1
- package/src/context/store.ts +4 -22
- package/src/fs/patches.ts +39 -0
- package/src/schedules/store.ts +11 -5
- package/src/tasks/store.ts +6 -6
- package/src/tools/file/edit.ts +2 -11
- package/src/tools/prompt/edit.ts +148 -0
- package/src/tools/prompt/read.ts +70 -0
- package/src/tools/registry.ts +9 -4
- package/src/tools/schedule/create.ts +3 -1
- package/src/tools/schedule/edit.ts +126 -0
- package/src/tools/skill/edit.ts +3 -33
- package/src/tools/task/create.ts +3 -1
- package/src/tools/task/delete.ts +3 -1
- package/src/tools/task/edit.ts +214 -0
- package/src/tools/task/update.ts +3 -1
- package/src/tools/tool.ts +7 -0
- package/src/tui/App.tsx +67 -7
- package/src/tui/components/ContextPanel.tsx +3 -2
- package/src/tui/components/HelpPanel.tsx +9 -7
- package/src/tui/components/SchedulePanel.tsx +2 -2
- package/src/tui/components/TaskPanel.tsx +2 -2
- package/src/tui/components/ThreadPanel.tsx +2 -2
- package/src/tui/components/ToolCall.tsx +14 -0
- package/src/tui/components/WorkerPanel.tsx +15 -4
- package/src/tools/context/update-beliefs.ts +0 -64
- package/src/tools/context/update-goals.ts +0 -64
|
@@ -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) ·
|
|
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" : ""} ·
|
|
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>;
|