botholomew 0.7.6 → 0.7.7

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.7.6",
3
+ "version": "0.7.7",
4
4
  "description": "Local, autonomous AI agent for knowledge work — works your task queue while you sleep.",
5
5
  "type": "module",
6
6
  "bin": {
@@ -75,18 +75,30 @@ export async function startDaemon(
75
75
  ? buildForegroundCallbacks()
76
76
  : undefined;
77
77
 
78
- logger.info(`Daemon started for ${projectDir} (PID ${process.pid})`);
78
+ logger.info(
79
+ `Daemon started ${new Date().toISOString()} for ${projectDir} (PID ${process.pid})`,
80
+ );
79
81
  logger.info(`Tick interval: ${config.tick_interval_seconds}s`);
80
82
 
83
+ let tickNum = 0;
81
84
  while (true) {
85
+ tickNum++;
82
86
  let didWork = false;
83
87
  try {
84
- didWork = await tick(projectDir, conn, config, mcpxClient, callbacks);
88
+ didWork = await tick(
89
+ projectDir,
90
+ conn,
91
+ config,
92
+ mcpxClient,
93
+ callbacks,
94
+ tickNum,
95
+ );
85
96
  } catch (err) {
86
97
  logger.error(`Tick failed: ${err}`);
87
98
  }
88
99
 
89
100
  if (!didWork) {
101
+ logger.phase("sleeping", `${config.tick_interval_seconds}s`);
90
102
  await Bun.sleep(config.tick_interval_seconds * 1000);
91
103
  }
92
104
  }
@@ -1,6 +1,7 @@
1
1
  import type { McpxClient } from "@evantahler/mcpx";
2
2
  import type { BotholomewConfig } from "../config/schemas.ts";
3
3
  import type { DbConnection } from "../db/connection.ts";
4
+ import { listSchedules } from "../db/schedules.ts";
4
5
  import {
5
6
  claimNextTask,
6
7
  resetStaleTasks,
@@ -20,8 +21,10 @@ export async function tick(
20
21
  config: Required<BotholomewConfig>,
21
22
  mcpxClient?: McpxClient | null,
22
23
  callbacks?: DaemonStreamCallbacks,
24
+ tickNum = 1,
23
25
  ): Promise<boolean> {
24
- logger.debug("Tick starting...");
26
+ const tickStart = Date.now();
27
+ logger.phase("tick-start", `#${tickNum}`);
25
28
 
26
29
  // Reset stale tasks stuck in in_progress
27
30
  const resetIds = await resetStaleTasks(
@@ -35,20 +38,27 @@ export async function tick(
35
38
  }
36
39
 
37
40
  // Process schedules (may create new tasks)
38
- try {
39
- await processSchedules(conn, config);
40
- } catch (err) {
41
- logger.error(`Schedule processing failed: ${err}`);
41
+ const enabledSchedules = await listSchedules(conn, { enabled: true });
42
+ if (enabledSchedules.length > 0) {
43
+ logger.phase("evaluating-schedules", `${enabledSchedules.length} enabled`);
44
+ try {
45
+ await processSchedules(conn, config);
46
+ } catch (err) {
47
+ logger.error(`Schedule processing failed: ${err}`);
48
+ }
42
49
  }
43
50
 
44
51
  // Claim a task
52
+ logger.phase("claiming-task");
45
53
  const task = await claimNextTask(conn);
46
54
  if (!task) {
47
- logger.debug("No tasks to work on. Sleeping.");
55
+ logger.info("No task claimed (queue empty or all blocked)");
56
+ const elapsed = ((Date.now() - tickStart) / 1000).toFixed(1);
57
+ logger.phase("tick-end", `#${tickNum} ${elapsed}s didWork=false`);
48
58
  return false;
49
59
  }
50
60
 
51
- logger.info(`Working on task: ${task.name} (${task.id})`);
61
+ logger.info(`Claimed task: ${task.name} (${task.id})`);
52
62
  callbacks?.onTaskStart(task);
53
63
 
54
64
  // Create a thread for this tick
@@ -115,5 +125,8 @@ export async function tick(
115
125
  await endThread(conn, threadId);
116
126
  }
117
127
 
128
+ const elapsed = ((Date.now() - tickStart) / 1000).toFixed(1);
129
+ logger.phase("tick-end", `#${tickNum} ${elapsed}s didWork=true`);
130
+
118
131
  return true;
119
132
  }
@@ -1,29 +1,42 @@
1
1
  import ansis from "ansis";
2
2
 
3
+ function ts(): string {
4
+ return ansis.gray(new Date().toTimeString().slice(0, 8));
5
+ }
6
+
3
7
  export const logger = {
4
8
  info(msg: string) {
5
- console.log(ansis.blue("ℹ"), msg);
9
+ console.log(ts(), ansis.blue("ℹ"), msg);
6
10
  },
7
11
 
8
12
  success(msg: string) {
9
- console.log(ansis.green("✓"), msg);
13
+ console.log(ts(), ansis.green("✓"), msg);
10
14
  },
11
15
 
12
16
  warn(msg: string) {
13
- console.log(ansis.yellow("⚠"), msg);
17
+ console.log(ts(), ansis.yellow("⚠"), msg);
14
18
  },
15
19
 
16
20
  error(msg: string) {
17
- console.error(ansis.red("✗"), msg);
21
+ console.error(ts(), ansis.red("✗"), msg);
18
22
  },
19
23
 
20
24
  debug(msg: string) {
21
25
  if (process.env.BOTHOLOMEW_DEBUG) {
22
- console.log(ansis.gray("·"), ansis.gray(msg));
26
+ console.log(ts(), ansis.gray("·"), ansis.gray(msg));
23
27
  }
24
28
  },
25
29
 
26
30
  dim(msg: string) {
27
- console.log(ansis.dim(msg));
31
+ console.log(ts(), ansis.dim(msg));
32
+ },
33
+
34
+ phase(name: string, detail?: string) {
35
+ const tag = ansis.magenta.bold(`[[${name}]]`);
36
+ if (detail) {
37
+ console.log(ts(), tag, ansis.dim(detail));
38
+ } else {
39
+ console.log(ts(), tag);
40
+ }
28
41
  },
29
42
  };