@metabob/minibob 0.1.2

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.
Files changed (174) hide show
  1. package/ARCHITECTURE.md +255 -0
  2. package/CHANGELOG.md +112 -0
  3. package/README.md +380 -0
  4. package/bin/minibob.js +36 -0
  5. package/dist/acp-gossip.d.ts +72 -0
  6. package/dist/acp-gossip.d.ts.map +1 -0
  7. package/dist/acp-gossip.js +156 -0
  8. package/dist/acp-gossip.js.map +1 -0
  9. package/dist/acp.d.ts +62 -0
  10. package/dist/acp.d.ts.map +1 -0
  11. package/dist/acp.js +292 -0
  12. package/dist/acp.js.map +1 -0
  13. package/dist/activity.d.ts +157 -0
  14. package/dist/activity.d.ts.map +1 -0
  15. package/dist/activity.js +518 -0
  16. package/dist/activity.js.map +1 -0
  17. package/dist/agent-runtime.d.ts +104 -0
  18. package/dist/agent-runtime.d.ts.map +1 -0
  19. package/dist/boredom.d.ts +125 -0
  20. package/dist/boredom.d.ts.map +1 -0
  21. package/dist/boredom.js +244 -0
  22. package/dist/boredom.js.map +1 -0
  23. package/dist/cli/acp-server.d.ts +23 -0
  24. package/dist/cli/acp-server.d.ts.map +1 -0
  25. package/dist/cli/burrow.d.ts +26 -0
  26. package/dist/cli/burrow.d.ts.map +1 -0
  27. package/dist/cli/doctor.d.ts +22 -0
  28. package/dist/cli/doctor.d.ts.map +1 -0
  29. package/dist/cli/goal.d.ts +22 -0
  30. package/dist/cli/goal.d.ts.map +1 -0
  31. package/dist/cli/index.d.ts +47 -0
  32. package/dist/cli/index.d.ts.map +1 -0
  33. package/dist/cli/instance-registry.d.ts +78 -0
  34. package/dist/cli/instance-registry.d.ts.map +1 -0
  35. package/dist/cli/observe.d.ts +35 -0
  36. package/dist/cli/observe.d.ts.map +1 -0
  37. package/dist/cli/vessel.d.ts +14 -0
  38. package/dist/cli/vessel.d.ts.map +1 -0
  39. package/dist/composition-observer.d.ts +96 -0
  40. package/dist/composition-observer.d.ts.map +1 -0
  41. package/dist/config.d.ts +36 -0
  42. package/dist/config.d.ts.map +1 -0
  43. package/dist/config.js +128 -0
  44. package/dist/config.js.map +1 -0
  45. package/dist/docker/Dockerfile +35 -0
  46. package/dist/environment.d.ts +72 -0
  47. package/dist/environment.d.ts.map +1 -0
  48. package/dist/environment.js +142 -0
  49. package/dist/environment.js.map +1 -0
  50. package/dist/goal-processor.d.ts +165 -0
  51. package/dist/goal-processor.d.ts.map +1 -0
  52. package/dist/helm/minibob-cluster/Chart.yaml +13 -0
  53. package/dist/helm/minibob-cluster/templates/_helpers.tpl +60 -0
  54. package/dist/helm/minibob-cluster/templates/configmap.yaml +11 -0
  55. package/dist/helm/minibob-cluster/templates/deployment.yaml +108 -0
  56. package/dist/helm/minibob-cluster/templates/secret.yaml +10 -0
  57. package/dist/helm/minibob-cluster/templates/service.yaml +37 -0
  58. package/dist/helm/minibob-cluster/values-local.yaml +41 -0
  59. package/dist/helm/minibob-cluster/values-production.yaml +57 -0
  60. package/dist/helm/minibob-cluster/values-testing-cluster.yaml +43 -0
  61. package/dist/helm/minibob-cluster/values.yaml +127 -0
  62. package/dist/improviser.d.ts +74 -0
  63. package/dist/improviser.d.ts.map +1 -0
  64. package/dist/impulse-filter.d.ts +74 -0
  65. package/dist/impulse-filter.d.ts.map +1 -0
  66. package/dist/impulse.d.ts +92 -0
  67. package/dist/impulse.d.ts.map +1 -0
  68. package/dist/impulse.js +234 -0
  69. package/dist/impulse.js.map +1 -0
  70. package/dist/lib.d.ts +29 -0
  71. package/dist/lib.d.ts.map +1 -0
  72. package/dist/lib.js +18561 -0
  73. package/dist/lib.js.map +98 -0
  74. package/dist/lifecycle-hooks.d.ts +99 -0
  75. package/dist/lifecycle-hooks.d.ts.map +1 -0
  76. package/dist/lifecycle-hooks.js +135 -0
  77. package/dist/lifecycle-hooks.js.map +1 -0
  78. package/dist/llm.d.ts +31 -0
  79. package/dist/llm.d.ts.map +1 -0
  80. package/dist/llm.js +349 -0
  81. package/dist/llm.js.map +1 -0
  82. package/dist/mcp-activity-bridge.d.ts +66 -0
  83. package/dist/mcp-activity-bridge.d.ts.map +1 -0
  84. package/dist/mcp-activity-bridge.js +126 -0
  85. package/dist/mcp-activity-bridge.js.map +1 -0
  86. package/dist/mcp.d.ts +216 -0
  87. package/dist/mcp.d.ts.map +1 -0
  88. package/dist/mcp.js +292 -0
  89. package/dist/mcp.js.map +1 -0
  90. package/dist/memory-agent.d.ts +92 -0
  91. package/dist/memory-agent.d.ts.map +1 -0
  92. package/dist/memory-agent.js +277 -0
  93. package/dist/memory-agent.js.map +1 -0
  94. package/dist/runtime-mapping.d.ts +97 -0
  95. package/dist/runtime-mapping.d.ts.map +1 -0
  96. package/dist/search-first-executor.d.ts +113 -0
  97. package/dist/search-first-executor.d.ts.map +1 -0
  98. package/dist/session.d.ts +48 -0
  99. package/dist/session.d.ts.map +1 -0
  100. package/dist/template-extractor.d.ts +9 -0
  101. package/dist/template-extractor.d.ts.map +1 -0
  102. package/dist/template-generator.d.ts +12 -0
  103. package/dist/template-generator.d.ts.map +1 -0
  104. package/dist/tools.d.ts +58 -0
  105. package/dist/tools.d.ts.map +1 -0
  106. package/dist/tools.js +771 -0
  107. package/dist/tools.js.map +1 -0
  108. package/dist/types.d.ts +503 -0
  109. package/dist/types.d.ts.map +1 -0
  110. package/dist/types.js +8 -0
  111. package/dist/types.js.map +1 -0
  112. package/dist/understanding/analyzer.d.ts +55 -0
  113. package/dist/understanding/analyzer.d.ts.map +1 -0
  114. package/dist/understanding/explorer.d.ts +73 -0
  115. package/dist/understanding/explorer.d.ts.map +1 -0
  116. package/dist/understanding/index.d.ts +7 -0
  117. package/dist/understanding/index.d.ts.map +1 -0
  118. package/dist/understanding/types.d.ts +136 -0
  119. package/dist/understanding/types.d.ts.map +1 -0
  120. package/dist/validation.d.ts +29 -0
  121. package/dist/validation.d.ts.map +1 -0
  122. package/dist/validation.js +106 -0
  123. package/dist/validation.js.map +1 -0
  124. package/dist/vessel-bootstrap.d.ts +190 -0
  125. package/dist/vessel-bootstrap.d.ts.map +1 -0
  126. package/dist/vessel-registry.d.ts +229 -0
  127. package/dist/vessel-registry.d.ts.map +1 -0
  128. package/index.ts +1329 -0
  129. package/package.json +54 -0
  130. package/src/acp-gossip.ts +193 -0
  131. package/src/acp.ts +362 -0
  132. package/src/activity.ts +1464 -0
  133. package/src/agent-runtime.ts +365 -0
  134. package/src/boredom.ts +423 -0
  135. package/src/cli/acp-server.ts +377 -0
  136. package/src/cli/burrow.ts +896 -0
  137. package/src/cli/doctor.ts +526 -0
  138. package/src/cli/goal.ts +224 -0
  139. package/src/cli/index.ts +147 -0
  140. package/src/cli/instance-registry.ts +271 -0
  141. package/src/cli/observe.ts +682 -0
  142. package/src/cli/vessel.ts +287 -0
  143. package/src/components/SystemOverview.tsx +331 -0
  144. package/src/composition-observer.ts +449 -0
  145. package/src/config.ts +172 -0
  146. package/src/environment.ts +167 -0
  147. package/src/goal-processor.ts +654 -0
  148. package/src/improviser.ts +591 -0
  149. package/src/impulse-filter.ts +273 -0
  150. package/src/impulse.ts +311 -0
  151. package/src/lib.ts +147 -0
  152. package/src/lifecycle-hooks.ts +181 -0
  153. package/src/llm.ts +434 -0
  154. package/src/mcp-activity-bridge.ts +158 -0
  155. package/src/mcp.ts +747 -0
  156. package/src/memory-agent.ts +316 -0
  157. package/src/runtime-mapping.ts +527 -0
  158. package/src/search-first-executor.ts +666 -0
  159. package/src/session.ts +141 -0
  160. package/src/template-extractor.ts +256 -0
  161. package/src/template-generator.ts +130 -0
  162. package/src/tools.ts +924 -0
  163. package/src/types.ts +497 -0
  164. package/src/understanding/analyzer.ts +354 -0
  165. package/src/understanding/explorer.ts +488 -0
  166. package/src/understanding/index.ts +27 -0
  167. package/src/understanding/types.ts +153 -0
  168. package/src/validation.ts +125 -0
  169. package/src/vessel-bootstrap.ts +440 -0
  170. package/src/vessel-registry.ts +621 -0
  171. package/templates/core/edit-file.json +85 -0
  172. package/templates/understanding/diagnose-problem.json +32 -0
  173. package/templates/understanding/explore-codebase-v2.json +57 -0
  174. package/templates/understanding/explore-codebase.json +37 -0
package/src/boredom.ts ADDED
@@ -0,0 +1,423 @@
1
+ /**
2
+ * minibob Boredom Task System
3
+ *
4
+ * Enables autonomous operation by polling backend for tasks when idle.
5
+ * Transforms minibob from request-response to continuous autonomous agent.
6
+ *
7
+ * Two execution modes:
8
+ * 1. Template-based: Load and execute a specific template
9
+ * 2. Goal-based: Use SearchFirstExecutor for dynamic decomposition and reuse
10
+ */
11
+
12
+ import { getMCPClient, isMCPEnabled } from "./mcp"
13
+ import { loadTemplateFromMCPOrLocal } from "./activity"
14
+ import { SearchFirstExecutor, type SearchFirstConfig } from "./search-first-executor"
15
+ import type { ActivityTemplate } from "./types"
16
+
17
+ // =============================================================================
18
+ // BOREDOM TASK TYPES
19
+ // =============================================================================
20
+
21
+ export interface BoredomTask {
22
+ id: string
23
+ templateId?: string // Optional - if provided, use template execution
24
+ goal?: string // Optional - if provided, use search-first execution
25
+ priority: "critical" | "high" | "medium" | "low"
26
+ variables: Record<string, unknown>
27
+ reason?: string
28
+ createdAt: number
29
+ assignedTo?: string
30
+ }
31
+
32
+ export interface BoredomTaskResult {
33
+ taskId: string
34
+ success: boolean
35
+ executionId?: string
36
+ duration?: number
37
+ error?: string
38
+ }
39
+
40
+ // =============================================================================
41
+ // BOREDOM TASK EXECUTOR
42
+ // =============================================================================
43
+
44
+ export interface BoredomExecutorConfig {
45
+ pollInterval?: number
46
+ idleThreshold?: number
47
+ // Template-based execution callback
48
+ onExecuteTask: (template: ActivityTemplate, variables: Record<string, unknown>, reason?: string) => Promise<unknown>
49
+ // LLM config for goal-based execution
50
+ llmConfig: {
51
+ provider: "anthropic" | "openai"
52
+ apiKey: string
53
+ model: string
54
+ workingDirectory: string
55
+ }
56
+ }
57
+
58
+ export class BoredomTaskExecutor {
59
+ private isRunning = false
60
+ private pollInterval: number
61
+ private idleThreshold: number
62
+ private lastActivityTime: number
63
+ private onExecuteTask: (template: ActivityTemplate, variables: Record<string, unknown>, reason?: string) => Promise<unknown>
64
+ private searchFirstExecutor: SearchFirstExecutor
65
+
66
+ constructor(config: BoredomExecutorConfig) {
67
+ this.pollInterval = config.pollInterval ?? 30000 // 30 seconds
68
+ this.idleThreshold = config.idleThreshold ?? 60000 // 60 seconds idle before fetching tasks
69
+ this.lastActivityTime = Date.now()
70
+ this.onExecuteTask = config.onExecuteTask
71
+
72
+ // Initialize SearchFirstExecutor for goal-based tasks
73
+ this.searchFirstExecutor = new SearchFirstExecutor({
74
+ provider: config.llmConfig.provider,
75
+ apiKey: config.llmConfig.apiKey,
76
+ model: config.llmConfig.model,
77
+ workingDirectory: config.llmConfig.workingDirectory,
78
+ maxSteps: 4, // Conservative for autonomous execution
79
+ maxTokensPerStep: 4096, // Prevent token overflow
80
+ })
81
+ }
82
+
83
+ /**
84
+ * Start the boredom task executor loop
85
+ *
86
+ * Only starts if:
87
+ * 1. MCP is enabled (backend connection available)
88
+ * 2. Cluster mode is enabled (3+ pods)
89
+ *
90
+ * Boredom system is disabled in local/Docker/single-pod environments to avoid resource waste.
91
+ */
92
+ start(clusterMode = false): void {
93
+ if (this.isRunning) {
94
+ console.log("[Boredom] Already running")
95
+ return
96
+ }
97
+
98
+ if (!isMCPEnabled()) {
99
+ console.log("[Boredom] MCP not enabled, boredom tasks disabled")
100
+ return
101
+ }
102
+
103
+ if (!clusterMode) {
104
+ console.log("[Boredom] Not in cluster mode, boredom tasks disabled")
105
+ return
106
+ }
107
+
108
+ this.isRunning = true
109
+ console.log(`[Boredom] Starting task executor (poll interval: ${this.pollInterval}ms, idle threshold: ${this.idleThreshold}ms)`)
110
+
111
+ this.loop().catch((error) => {
112
+ console.error("[Boredom] Fatal error in loop:", error)
113
+ this.isRunning = false
114
+ })
115
+ }
116
+
117
+ /**
118
+ * Stop the boredom task executor
119
+ */
120
+ stop(): void {
121
+ this.isRunning = false
122
+ console.log("[Boredom] Stopping task executor")
123
+ }
124
+
125
+ /**
126
+ * Mark activity (resets idle timer)
127
+ */
128
+ markActivity(): void {
129
+ this.lastActivityTime = Date.now()
130
+ }
131
+
132
+ /**
133
+ * Check if vessel is idle
134
+ */
135
+ isIdle(): boolean {
136
+ const idleTime = Date.now() - this.lastActivityTime
137
+ return idleTime >= this.idleThreshold
138
+ }
139
+
140
+ /**
141
+ * Main executor loop
142
+ */
143
+ private async loop(): Promise<void> {
144
+ while (this.isRunning) {
145
+ try {
146
+ // Wait for poll interval
147
+ await this.sleep(this.pollInterval)
148
+
149
+ // Skip if not idle
150
+ if (!this.isIdle()) {
151
+ continue
152
+ }
153
+
154
+ // Fetch tasks from backend
155
+ const tasks = await this.fetchTasks()
156
+ if (tasks.length === 0) {
157
+ continue
158
+ }
159
+
160
+ console.log(`[Boredom] Found ${tasks.length} available task(s)`)
161
+
162
+ // Execute highest priority task
163
+ const task = tasks[0]
164
+ if (!task) continue
165
+ await this.executeTask(task)
166
+
167
+ } catch (error) {
168
+ console.error("[Boredom] Error in loop:", error)
169
+ // Continue running despite errors
170
+ }
171
+ }
172
+ }
173
+
174
+ /**
175
+ * Fetch available boredom tasks from backend
176
+ */
177
+ private async fetchTasks(): Promise<BoredomTask[]> {
178
+ const mcp = getMCPClient()
179
+ if (!mcp) return []
180
+
181
+ try {
182
+ const response = await fetch(`${(mcp as any).endpoint}/boredom-tasks`, {
183
+ method: "GET",
184
+ headers: {
185
+ "Content-Type": "application/json",
186
+ },
187
+ })
188
+
189
+ if (!response.ok) {
190
+ if (response.status === 404) {
191
+ // Endpoint not implemented yet
192
+ return []
193
+ }
194
+ throw new Error(`Failed to fetch tasks: ${response.status}`)
195
+ }
196
+
197
+ const data = await response.json() as { tasks?: BoredomTask[] }
198
+ const tasks = data.tasks ?? []
199
+
200
+ // Sort by priority
201
+ const sorted = tasks.sort((a, b) => {
202
+ const priorityOrder = { critical: 0, high: 1, medium: 2, low: 3 }
203
+ return priorityOrder[a.priority] - priorityOrder[b.priority]
204
+ })
205
+
206
+ // Convert debug/optimization templates to goal-based tasks
207
+ return sorted.map(task => this.convertToGoalTaskIfApplicable(task))
208
+ } catch (error) {
209
+ console.error("[Boredom] Error fetching tasks:", error)
210
+ return []
211
+ }
212
+ }
213
+
214
+ /**
215
+ * Convert template-based tasks to goal-based tasks when appropriate
216
+ *
217
+ * This enables backwards compatibility: the backend can still emit
218
+ * template-based tasks, but we convert debug/optimization tasks to
219
+ * use the SearchFirstExecutor for better reuse and decomposition.
220
+ */
221
+ private convertToGoalTaskIfApplicable(task: BoredomTask): BoredomTask {
222
+ // If task already has a goal, use as-is
223
+ if (task.goal) {
224
+ return task
225
+ }
226
+
227
+ // Convert debug templates to goal-based tasks
228
+ if (task.templateId?.startsWith("debug-")) {
229
+ const vars = task.variables as {
230
+ failingTemplateId?: string
231
+ successRate?: number
232
+ totalExecutions?: number
233
+ recentFailures?: unknown[]
234
+ }
235
+
236
+ console.log(`[Boredom] Converting debug task to goal-based: ${task.id}`)
237
+
238
+ return {
239
+ ...task,
240
+ templateId: undefined, // Remove template - use goal instead
241
+ goal: `Investigate why activity "${vars.failingTemplateId}" has ${vars.successRate ?? 0}% success rate after ${vars.totalExecutions ?? 0} executions. Identify the root cause and suggest fixes.`,
242
+ }
243
+ }
244
+
245
+ // Convert optimization templates to goal-based tasks
246
+ if (task.templateId?.startsWith("optimize-") || task.templateId?.startsWith("improve-")) {
247
+ console.log(`[Boredom] Converting optimization task to goal-based: ${task.id}`)
248
+
249
+ return {
250
+ ...task,
251
+ templateId: undefined,
252
+ goal: `Optimize activity "${(task.variables as { targetTemplateId?: string }).targetTemplateId}". Analyze performance metrics and suggest improvements.`,
253
+ }
254
+ }
255
+
256
+ // Keep other tasks as template-based
257
+ return task
258
+ }
259
+
260
+ /**
261
+ * Execute a boredom task
262
+ *
263
+ * Two execution modes:
264
+ * 1. Goal-based: Use SearchFirstExecutor for dynamic step decomposition
265
+ * 2. Template-based: Load and execute a specific template (legacy)
266
+ */
267
+ private async executeTask(task: BoredomTask): Promise<void> {
268
+ const result: BoredomTaskResult = {
269
+ taskId: task.id,
270
+ success: false,
271
+ }
272
+
273
+ const startTime = Date.now()
274
+
275
+ try {
276
+ // Mode 1: Goal-based execution (preferred)
277
+ if (task.goal) {
278
+ console.log(`[Boredom] Executing goal-based task: ${task.id}`)
279
+ console.log(`[Boredom] Goal: ${task.goal}`)
280
+
281
+ const goalResult = await this.searchFirstExecutor.execute(
282
+ task.goal,
283
+ {
284
+ ...task.variables,
285
+ taskId: task.id,
286
+ reason: task.reason,
287
+ }
288
+ )
289
+
290
+ result.success = goalResult.completed
291
+ result.duration = Date.now() - startTime
292
+
293
+ if (goalResult.completed) {
294
+ console.log(`[Boredom] ✓ Goal completed: ${task.id} in ${result.duration}ms`)
295
+ console.log(`[Boredom] Summary: ${goalResult.summary}`)
296
+ } else {
297
+ result.error = `Goal partially completed: ${goalResult.results.filter(r => r.status === "failed").map(r => r.error).join("; ")}`
298
+ console.log(`[Boredom] ⚠ Goal partially completed: ${task.id}`)
299
+ }
300
+ }
301
+ // Mode 2: Template-based execution (legacy)
302
+ else if (task.templateId) {
303
+ console.log(`[Boredom] Executing template-based task: ${task.id} (template: ${task.templateId})`)
304
+
305
+ const template = await loadTemplateFromMCPOrLocal(task.templateId)
306
+
307
+ const execution = await this.onExecuteTask(
308
+ template,
309
+ task.variables,
310
+ task.reason ?? `Boredom task: ${task.id}`
311
+ )
312
+
313
+ result.success = true
314
+ result.executionId = (execution as any).id
315
+ result.duration = Date.now() - startTime
316
+
317
+ console.log(`[Boredom] ✓ Task completed: ${task.id} in ${result.duration}ms`)
318
+ }
319
+ // No goal or template - invalid task
320
+ else {
321
+ throw new Error("Task must have either 'goal' or 'templateId'")
322
+ }
323
+
324
+ } catch (error) {
325
+ result.success = false
326
+ result.error = error instanceof Error ? error.message : String(error)
327
+ result.duration = Date.now() - startTime
328
+ console.error(`[Boredom] ✗ Task failed: ${task.id}`, error)
329
+ }
330
+
331
+ // Report result to backend
332
+ await this.reportResult(result)
333
+
334
+ // Mark activity to reset idle timer
335
+ this.markActivity()
336
+ }
337
+
338
+ /**
339
+ * Report task execution result to backend
340
+ */
341
+ private async reportResult(result: BoredomTaskResult): Promise<void> {
342
+ const mcp = getMCPClient()
343
+ if (!mcp) return
344
+
345
+ try {
346
+ const response = await fetch(`${(mcp as any).endpoint}/boredom-tasks/${result.taskId}/result`, {
347
+ method: "POST",
348
+ headers: {
349
+ "Content-Type": "application/json",
350
+ },
351
+ body: JSON.stringify(result),
352
+ })
353
+
354
+ if (!response.ok) {
355
+ console.warn(`[Boredom] Failed to report result: ${response.status}`)
356
+ }
357
+ } catch (error) {
358
+ console.error("[Boredom] Error reporting result:", error)
359
+ }
360
+ }
361
+
362
+ /**
363
+ * Sleep helper
364
+ */
365
+ private sleep(ms: number): Promise<void> {
366
+ return new Promise((resolve) => setTimeout(resolve, ms))
367
+ }
368
+ }
369
+
370
+ // =============================================================================
371
+ // SINGLETON BOREDOM EXECUTOR
372
+ // =============================================================================
373
+
374
+ let boredomExecutor: BoredomTaskExecutor | null = null
375
+
376
+ /**
377
+ * Initialize boredom task executor
378
+ */
379
+ export function initializeBoredom(config: BoredomExecutorConfig): BoredomTaskExecutor {
380
+ if (boredomExecutor) {
381
+ console.warn("[Boredom] Already initialized")
382
+ return boredomExecutor
383
+ }
384
+
385
+ boredomExecutor = new BoredomTaskExecutor(config)
386
+ return boredomExecutor
387
+ }
388
+
389
+ /**
390
+ * Get boredom executor instance
391
+ */
392
+ export function getBoredomExecutor(): BoredomTaskExecutor | null {
393
+ return boredomExecutor
394
+ }
395
+
396
+ /**
397
+ * Start boredom task execution
398
+ *
399
+ * @param clusterMode Whether cluster mode is enabled (required for boredom to start)
400
+ */
401
+ export function startBoredom(clusterMode = false): void {
402
+ if (boredomExecutor) {
403
+ boredomExecutor.start(clusterMode)
404
+ }
405
+ }
406
+
407
+ /**
408
+ * Stop boredom task execution
409
+ */
410
+ export function stopBoredom(): void {
411
+ if (boredomExecutor) {
412
+ boredomExecutor.stop()
413
+ }
414
+ }
415
+
416
+ /**
417
+ * Mark activity (called when handling user requests)
418
+ */
419
+ export function markBoredomActivity(): void {
420
+ if (boredomExecutor) {
421
+ boredomExecutor.markActivity()
422
+ }
423
+ }