@runcore-sh/runcore 0.4.0 → 0.5.0

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 (351) hide show
  1. package/dictionary.json +2 -2
  2. package/dist/activity/log.js +2 -2
  3. package/dist/activity/log.js.map +1 -1
  4. package/dist/agents/governed-spawn.d.ts.map +1 -1
  5. package/dist/cli.js +101 -11
  6. package/dist/cli.js.map +1 -1
  7. package/dist/extensions/cache.d.ts +57 -0
  8. package/dist/extensions/cache.d.ts.map +1 -0
  9. package/dist/extensions/cache.js +173 -0
  10. package/dist/extensions/cache.js.map +1 -0
  11. package/dist/extensions/client.d.ts +55 -0
  12. package/dist/extensions/client.d.ts.map +1 -0
  13. package/dist/extensions/client.js +120 -0
  14. package/dist/extensions/client.js.map +1 -0
  15. package/dist/extensions/index.d.ts +13 -0
  16. package/dist/extensions/index.d.ts.map +1 -0
  17. package/dist/extensions/index.js +12 -0
  18. package/dist/extensions/index.js.map +1 -0
  19. package/dist/extensions/loader.d.ts +50 -0
  20. package/dist/extensions/loader.d.ts.map +1 -0
  21. package/dist/extensions/loader.js +166 -0
  22. package/dist/extensions/loader.js.map +1 -0
  23. package/dist/extensions/manifest.d.ts +38 -0
  24. package/dist/extensions/manifest.d.ts.map +1 -0
  25. package/dist/extensions/manifest.js +17 -0
  26. package/dist/extensions/manifest.js.map +1 -0
  27. package/dist/extensions/stubs.d.ts +27 -0
  28. package/dist/extensions/stubs.d.ts.map +1 -0
  29. package/dist/extensions/stubs.js +45 -0
  30. package/dist/extensions/stubs.js.map +1 -0
  31. package/dist/lib/audit.js +2 -2
  32. package/dist/lib/audit.js.map +1 -1
  33. package/dist/lib/brain-migrate.d.ts +21 -0
  34. package/dist/lib/brain-migrate.d.ts.map +1 -0
  35. package/dist/lib/brain-migrate.js +137 -0
  36. package/dist/lib/brain-migrate.js.map +1 -0
  37. package/dist/lib/paths.d.ts +27 -0
  38. package/dist/lib/paths.d.ts.map +1 -1
  39. package/dist/lib/paths.js +65 -0
  40. package/dist/lib/paths.js.map +1 -1
  41. package/dist/llm/call-log.d.ts +40 -0
  42. package/dist/llm/call-log.d.ts.map +1 -0
  43. package/dist/llm/call-log.js +35 -0
  44. package/dist/llm/call-log.js.map +1 -0
  45. package/dist/llm/complete.d.ts +6 -0
  46. package/dist/llm/complete.d.ts.map +1 -1
  47. package/dist/llm/complete.js +27 -0
  48. package/dist/llm/complete.js.map +1 -1
  49. package/dist/mcp-server.js +118 -2
  50. package/dist/mcp-server.js.map +1 -1
  51. package/dist/memory/file-backed.d.ts +4 -0
  52. package/dist/memory/file-backed.d.ts.map +1 -1
  53. package/dist/memory/file-backed.js +4 -0
  54. package/dist/memory/file-backed.js.map +1 -1
  55. package/dist/memory/vector-index.d.ts +4 -12
  56. package/dist/memory/vector-index.d.ts.map +1 -1
  57. package/dist/memory/vector-index.js +11 -93
  58. package/dist/memory/vector-index.js.map +1 -1
  59. package/dist/search/brain-docs.d.ts +17 -7
  60. package/dist/search/brain-docs.d.ts.map +1 -1
  61. package/dist/search/brain-docs.js +170 -52
  62. package/dist/search/brain-docs.js.map +1 -1
  63. package/dist/search/brain-rag.d.ts +45 -0
  64. package/dist/search/brain-rag.d.ts.map +1 -0
  65. package/dist/search/brain-rag.js +275 -0
  66. package/dist/search/brain-rag.js.map +1 -0
  67. package/dist/search/chunker.d.ts +24 -0
  68. package/dist/search/chunker.d.ts.map +1 -0
  69. package/dist/search/chunker.js +95 -0
  70. package/dist/search/chunker.js.map +1 -0
  71. package/dist/search/embedder.d.ts +16 -0
  72. package/dist/search/embedder.d.ts.map +1 -0
  73. package/dist/search/embedder.js +108 -0
  74. package/dist/search/embedder.js.map +1 -0
  75. package/dist/search/file-watcher.d.ts +11 -0
  76. package/dist/search/file-watcher.d.ts.map +1 -0
  77. package/dist/search/file-watcher.js +86 -0
  78. package/dist/search/file-watcher.js.map +1 -0
  79. package/dist/server.d.ts.map +1 -1
  80. package/dist/server.js +168 -20
  81. package/dist/server.js.map +1 -1
  82. package/dist/sessions/store.d.ts +9 -0
  83. package/dist/sessions/store.d.ts.map +1 -1
  84. package/dist/sessions/store.js.map +1 -1
  85. package/dist/settings.d.ts +26 -0
  86. package/dist/settings.d.ts.map +1 -1
  87. package/dist/settings.js +78 -2
  88. package/dist/settings.js.map +1 -1
  89. package/dist/tracing/init.d.ts +1 -1
  90. package/dist/tracing/init.d.ts.map +1 -1
  91. package/dist/utils/logger.js +2 -2
  92. package/dist/utils/logger.js.map +1 -1
  93. package/module-tiers.json +164 -0
  94. package/package.json +9 -13
  95. package/public/avatar/cache/1184385ec5522b57.mp4 +0 -0
  96. package/public/avatar/cache/1f15f6a1ebd7e439.mp4 +0 -0
  97. package/public/avatar/cache/2c7e47ff0bdeb8d1.mp4 +0 -0
  98. package/public/avatar/cache/5f308566f7abb8f2.mp4 +0 -0
  99. package/public/avatar/cache/62f9cfba848d724e.mp4 +0 -0
  100. package/public/avatar/cache/6d64e657e6bf2aab.mp4 +0 -0
  101. package/public/avatar/cache/763ad0349e0b6f26.mp4 +0 -0
  102. package/public/avatar/cache/81a516cfd461b2b9.mp4 +0 -0
  103. package/public/avatar/cache/9366de15fd6910ca.mp4 +0 -0
  104. package/public/avatar/cache/ade41a846b283895.mp4 +0 -0
  105. package/public/avatar/cache/b6066e5c65383eec.mp4 +0 -0
  106. package/public/avatar/cache/edadb75d37891fc7.mp4 +0 -0
  107. package/public/avatar/cache/f0ae159640621dd9.mp4 +0 -0
  108. package/public/avatar/cache/fc2e5419adf29d96.mp4 +0 -0
  109. package/public/index.html +379 -59
  110. package/dist/agents/autonomous.js +0 -749
  111. package/dist/agents/autonomous.js.map +0 -1
  112. package/dist/agents/commit.js +0 -113
  113. package/dist/agents/commit.js.map +0 -1
  114. package/dist/agents/continue.js +0 -158
  115. package/dist/agents/continue.js.map +0 -1
  116. package/dist/agents/cooldown.js +0 -397
  117. package/dist/agents/cooldown.js.map +0 -1
  118. package/dist/agents/dedup-guard.js +0 -131
  119. package/dist/agents/dedup-guard.js.map +0 -1
  120. package/dist/agents/feed.js +0 -176
  121. package/dist/agents/feed.js.map +0 -1
  122. package/dist/agents/governance.js +0 -292
  123. package/dist/agents/governance.js.map +0 -1
  124. package/dist/agents/governed-spawn.js +0 -192
  125. package/dist/agents/governed-spawn.js.map +0 -1
  126. package/dist/agents/heartbeat.js +0 -324
  127. package/dist/agents/heartbeat.js.map +0 -1
  128. package/dist/agents/instance-manager.js +0 -850
  129. package/dist/agents/instance-manager.js.map +0 -1
  130. package/dist/agents/issue-reporter.js +0 -123
  131. package/dist/agents/issue-reporter.js.map +0 -1
  132. package/dist/agents/issues.js +0 -141
  133. package/dist/agents/issues.js.map +0 -1
  134. package/dist/agents/locks.js +0 -234
  135. package/dist/agents/locks.js.map +0 -1
  136. package/dist/agents/memory.js +0 -93
  137. package/dist/agents/memory.js.map +0 -1
  138. package/dist/agents/monitor.js +0 -235
  139. package/dist/agents/monitor.js.map +0 -1
  140. package/dist/agents/orchestration.js +0 -715
  141. package/dist/agents/orchestration.js.map +0 -1
  142. package/dist/agents/recover.js +0 -166
  143. package/dist/agents/recover.js.map +0 -1
  144. package/dist/agents/reflection.js +0 -199
  145. package/dist/agents/reflection.js.map +0 -1
  146. package/dist/agents/runtime/bus.js +0 -174
  147. package/dist/agents/runtime/bus.js.map +0 -1
  148. package/dist/agents/runtime/config.js +0 -101
  149. package/dist/agents/runtime/config.js.map +0 -1
  150. package/dist/agents/runtime/driver.js +0 -214
  151. package/dist/agents/runtime/driver.js.map +0 -1
  152. package/dist/agents/runtime/errors.js +0 -40
  153. package/dist/agents/runtime/errors.js.map +0 -1
  154. package/dist/agents/runtime/index.js +0 -54
  155. package/dist/agents/runtime/index.js.map +0 -1
  156. package/dist/agents/runtime/lifecycle.js +0 -116
  157. package/dist/agents/runtime/lifecycle.js.map +0 -1
  158. package/dist/agents/runtime/manager.js +0 -948
  159. package/dist/agents/runtime/manager.js.map +0 -1
  160. package/dist/agents/runtime/registry.js +0 -195
  161. package/dist/agents/runtime/registry.js.map +0 -1
  162. package/dist/agents/runtime/resources.js +0 -146
  163. package/dist/agents/runtime/resources.js.map +0 -1
  164. package/dist/agents/runtime/types.js +0 -24
  165. package/dist/agents/runtime/types.js.map +0 -1
  166. package/dist/agents/spawn-policy.js +0 -202
  167. package/dist/agents/spawn-policy.js.map +0 -1
  168. package/dist/agents/spawn.js +0 -970
  169. package/dist/agents/spawn.js.map +0 -1
  170. package/dist/agents/triage.js +0 -81
  171. package/dist/agents/triage.js.map +0 -1
  172. package/dist/agents/workflow.js +0 -543
  173. package/dist/agents/workflow.js.map +0 -1
  174. package/dist/avatar/client.js +0 -172
  175. package/dist/avatar/client.js.map +0 -1
  176. package/dist/avatar/sidecar.js +0 -125
  177. package/dist/avatar/sidecar.js.map +0 -1
  178. package/dist/browser/sessions.js +0 -122
  179. package/dist/browser/sessions.js.map +0 -1
  180. package/dist/capabilities/definitions/browser.js +0 -242
  181. package/dist/capabilities/definitions/browser.js.map +0 -1
  182. package/dist/channels/whatsapp.js +0 -200
  183. package/dist/channels/whatsapp.js.map +0 -1
  184. package/dist/credentials/store.js +0 -189
  185. package/dist/credentials/store.js.map +0 -1
  186. package/dist/files/deep-index.js +0 -337
  187. package/dist/files/deep-index.js.map +0 -1
  188. package/dist/files/extract.js +0 -33
  189. package/dist/files/extract.js.map +0 -1
  190. package/dist/files/gdrive.js +0 -246
  191. package/dist/files/gdrive.js.map +0 -1
  192. package/dist/github/client.js +0 -408
  193. package/dist/github/client.js.map +0 -1
  194. package/dist/github/commit-analysis.js +0 -276
  195. package/dist/github/commit-analysis.js.map +0 -1
  196. package/dist/github/contributor-stats.js +0 -119
  197. package/dist/github/contributor-stats.js.map +0 -1
  198. package/dist/github/issue-sla.js +0 -220
  199. package/dist/github/issue-sla.js.map +0 -1
  200. package/dist/github/issue-triage.js +0 -286
  201. package/dist/github/issue-triage.js.map +0 -1
  202. package/dist/github/pr-readiness.js +0 -197
  203. package/dist/github/pr-readiness.js.map +0 -1
  204. package/dist/github/pr-review.js +0 -410
  205. package/dist/github/pr-review.js.map +0 -1
  206. package/dist/github/release-notes.js +0 -227
  207. package/dist/github/release-notes.js.map +0 -1
  208. package/dist/github/repo-health.js +0 -303
  209. package/dist/github/repo-health.js.map +0 -1
  210. package/dist/github/retry.js +0 -117
  211. package/dist/github/retry.js.map +0 -1
  212. package/dist/github/types.js +0 -8
  213. package/dist/github/types.js.map +0 -1
  214. package/dist/github/webhooks.js +0 -153
  215. package/dist/github/webhooks.js.map +0 -1
  216. package/dist/google/auth.js +0 -325
  217. package/dist/google/auth.js.map +0 -1
  218. package/dist/google/calendar-timer.js +0 -91
  219. package/dist/google/calendar-timer.js.map +0 -1
  220. package/dist/google/calendar.js +0 -270
  221. package/dist/google/calendar.js.map +0 -1
  222. package/dist/google/docs.js +0 -309
  223. package/dist/google/docs.js.map +0 -1
  224. package/dist/google/gmail-send.js +0 -219
  225. package/dist/google/gmail-send.js.map +0 -1
  226. package/dist/google/gmail-timer.js +0 -223
  227. package/dist/google/gmail-timer.js.map +0 -1
  228. package/dist/google/gmail.js +0 -470
  229. package/dist/google/gmail.js.map +0 -1
  230. package/dist/google/plugin.js +0 -169
  231. package/dist/google/plugin.js.map +0 -1
  232. package/dist/google/tasks-timer.js +0 -107
  233. package/dist/google/tasks-timer.js.map +0 -1
  234. package/dist/google/tasks.js +0 -331
  235. package/dist/google/tasks.js.map +0 -1
  236. package/dist/google/temporal.js +0 -176
  237. package/dist/google/temporal.js.map +0 -1
  238. package/dist/integrations/gate.js +0 -100
  239. package/dist/integrations/gate.js.map +0 -1
  240. package/dist/integrations/github.js +0 -331
  241. package/dist/integrations/github.js.map +0 -1
  242. package/dist/integrations/google-tasks.js +0 -432
  243. package/dist/integrations/google-tasks.js.map +0 -1
  244. package/dist/mdns.js +0 -110
  245. package/dist/mdns.js.map +0 -1
  246. package/dist/notifications/channel.js +0 -83
  247. package/dist/notifications/channel.js.map +0 -1
  248. package/dist/notifications/channels/adapter.js +0 -55
  249. package/dist/notifications/channels/adapter.js.map +0 -1
  250. package/dist/notifications/channels/index.js +0 -6
  251. package/dist/notifications/channels/index.js.map +0 -1
  252. package/dist/notifications/channels/log.js +0 -29
  253. package/dist/notifications/channels/log.js.map +0 -1
  254. package/dist/notifications/email.js +0 -72
  255. package/dist/notifications/email.js.map +0 -1
  256. package/dist/notifications/engine.js +0 -198
  257. package/dist/notifications/engine.js.map +0 -1
  258. package/dist/notifications/index.js +0 -24
  259. package/dist/notifications/index.js.map +0 -1
  260. package/dist/notifications/phone.js +0 -48
  261. package/dist/notifications/phone.js.map +0 -1
  262. package/dist/notifications/sms.js +0 -65
  263. package/dist/notifications/sms.js.map +0 -1
  264. package/dist/notifications/types.js +0 -14
  265. package/dist/notifications/types.js.map +0 -1
  266. package/dist/notifications/webhook.js +0 -65
  267. package/dist/notifications/webhook.js.map +0 -1
  268. package/dist/resend/inbox.js +0 -199
  269. package/dist/resend/inbox.js.map +0 -1
  270. package/dist/resend/webhooks.js +0 -244
  271. package/dist/resend/webhooks.js.map +0 -1
  272. package/dist/search/browse.js +0 -225
  273. package/dist/search/browse.js.map +0 -1
  274. package/dist/search/perplexity.js +0 -41
  275. package/dist/search/perplexity.js.map +0 -1
  276. package/dist/slack/channels.js +0 -277
  277. package/dist/slack/channels.js.map +0 -1
  278. package/dist/slack/client.js +0 -468
  279. package/dist/slack/client.js.map +0 -1
  280. package/dist/slack/retry.js +0 -100
  281. package/dist/slack/retry.js.map +0 -1
  282. package/dist/slack/types.js +0 -52
  283. package/dist/slack/types.js.map +0 -1
  284. package/dist/slack/webhooks.js +0 -285
  285. package/dist/slack/webhooks.js.map +0 -1
  286. package/dist/stt/client.js +0 -66
  287. package/dist/stt/client.js.map +0 -1
  288. package/dist/stt/sidecar.js +0 -115
  289. package/dist/stt/sidecar.js.map +0 -1
  290. package/dist/tracing/bridge.js +0 -70
  291. package/dist/tracing/bridge.js.map +0 -1
  292. package/dist/tracing/correlation.js +0 -49
  293. package/dist/tracing/correlation.js.map +0 -1
  294. package/dist/tracing/index.js +0 -18
  295. package/dist/tracing/index.js.map +0 -1
  296. package/dist/tracing/init.js +0 -81
  297. package/dist/tracing/init.js.map +0 -1
  298. package/dist/tracing/instrument.js +0 -145
  299. package/dist/tracing/instrument.js.map +0 -1
  300. package/dist/tracing/middleware.js +0 -69
  301. package/dist/tracing/middleware.js.map +0 -1
  302. package/dist/tracing/tracer.js +0 -327
  303. package/dist/tracing/tracer.js.map +0 -1
  304. package/dist/tts/client.js +0 -48
  305. package/dist/tts/client.js.map +0 -1
  306. package/dist/tts/sidecar.js +0 -148
  307. package/dist/tts/sidecar.js.map +0 -1
  308. package/dist/twilio/call.js +0 -79
  309. package/dist/twilio/call.js.map +0 -1
  310. package/dist/vault/matcher.js +0 -197
  311. package/dist/vault/matcher.js.map +0 -1
  312. package/dist/vault/personal.js +0 -163
  313. package/dist/vault/personal.js.map +0 -1
  314. package/dist/vault/policy.js +0 -159
  315. package/dist/vault/policy.js.map +0 -1
  316. package/dist/vault/store.js +0 -122
  317. package/dist/vault/store.js.map +0 -1
  318. package/dist/vault/transfer.js +0 -188
  319. package/dist/vault/transfer.js.map +0 -1
  320. package/dist/volumes/index.js +0 -2
  321. package/dist/volumes/index.js.map +0 -1
  322. package/dist/volumes/manager.js +0 -462
  323. package/dist/volumes/manager.js.map +0 -1
  324. package/dist/volumes/types.js +0 -8
  325. package/dist/volumes/types.js.map +0 -1
  326. package/dist/webhooks/config.js +0 -214
  327. package/dist/webhooks/config.js.map +0 -1
  328. package/dist/webhooks/event-log.js +0 -132
  329. package/dist/webhooks/event-log.js.map +0 -1
  330. package/dist/webhooks/handler.js +0 -103
  331. package/dist/webhooks/handler.js.map +0 -1
  332. package/dist/webhooks/handlers.js +0 -231
  333. package/dist/webhooks/handlers.js.map +0 -1
  334. package/dist/webhooks/index.js +0 -33
  335. package/dist/webhooks/index.js.map +0 -1
  336. package/dist/webhooks/mount.js +0 -400
  337. package/dist/webhooks/mount.js.map +0 -1
  338. package/dist/webhooks/registry.js +0 -143
  339. package/dist/webhooks/registry.js.map +0 -1
  340. package/dist/webhooks/relay.js +0 -53
  341. package/dist/webhooks/relay.js.map +0 -1
  342. package/dist/webhooks/retry.js +0 -270
  343. package/dist/webhooks/retry.js.map +0 -1
  344. package/dist/webhooks/router.js +0 -290
  345. package/dist/webhooks/router.js.map +0 -1
  346. package/dist/webhooks/twilio.js +0 -129
  347. package/dist/webhooks/twilio.js.map +0 -1
  348. package/dist/webhooks/types.js +0 -8
  349. package/dist/webhooks/types.js.map +0 -1
  350. package/dist/webhooks/verify.js +0 -154
  351. package/dist/webhooks/verify.js.map +0 -1
@@ -1,749 +0,0 @@
1
- /**
2
- * Autonomous work loop — the missing "first nudge" that makes the agent proactive.
3
- *
4
- * Two entry points:
5
- * 1. Timer: fires 60s after boot, then every 15 min. Checks board, spawns agents.
6
- * 2. continueAfterBatch(): called when a batch completes, decides what's next.
7
- *
8
- * Both use planAndSpawn() which:
9
- * - Reads board state directly (no HTTP, no SSE, no session validation)
10
- * - Asks the LLM to pick tasks and write agent prompts
11
- * - Parses [AGENT_REQUEST] blocks from the response
12
- * - Spawns agents via submitTask()
13
- */
14
- import { getBoardProvider } from "../board/provider.js";
15
- import { submitTask } from "./index.js";
16
- import { activeAgentCount, generateBridgeReport } from "./spawn.js";
17
- import { commitAgentBatch } from "./commit.js";
18
- import { listTasks as listAgentTasks } from "./store.js";
19
- import { logActivity } from "../activity/log.js";
20
- import { checkResendInbox } from "../resend/inbox.js";
21
- import { pushNotification } from "../goals/notifications.js";
22
- import { completeChat } from "../llm/complete.js";
23
- import { LLMError } from "../llm/errors.js";
24
- import { resolveProvider } from "../settings.js";
25
- import { resolveEnv, getInstanceName, getInstanceNameLower } from "../instance.js";
26
- import { createLogger } from "../utils/logger.js";
27
- import { reflectOnSession } from "./reflection.js";
28
- import { TaskCooldownManager } from "./cooldown.js";
29
- import { checkDedup } from "./dedup-guard.js";
30
- import { recordAutonomousAction, recordDedupBlock } from "../metrics/firewall-metrics.js";
31
- import { getCapabilityRegistry } from "../capabilities/index.js";
32
- import { getPressureIntegrator } from "../pulse/pressure.js";
33
- const log = createLogger("autonomous");
34
- // ─── Config ──────────────────────────────────────────────────────────────────
35
- const AUTONOMOUS_INTERVAL_MS = 60 * 60 * 1000; // 60 min (coma failsafe — primary trigger is now tension-based)
36
- const FIRST_CHECK_DELAY_MS = 60 * 1000; // 60s after boot
37
- const MAX_CONTINUATION_ROUNDS = 5;
38
- const MAX_AGENTS_PER_ROUND = 5;
39
- /** Model for the planner LLM — needs to be capable enough for structured output. */
40
- const PLANNER_MODEL = "anthropic/claude-sonnet-4";
41
- // ─── State ───────────────────────────────────────────────────────────────────
42
- let timer = null;
43
- let firstCheckTimer = null;
44
- const continuationRounds = new Map();
45
- let autonomousSessionCounter = 0;
46
- /** Guard against concurrent planAndSpawn calls (timer vs continuation race). */
47
- let planningInProgress = false;
48
- /**
49
- * Per-session cumulative failure counter.
50
- * If a session accumulates too many total failures across all continuation rounds,
51
- * stop the session entirely. This prevents the scenario where mixed success/failure
52
- * batches keep the session alive while repeatedly retrying the same failing tasks.
53
- */
54
- const sessionCumulativeFailures = new Map();
55
- const MAX_SESSION_CUMULATIVE_FAILURES = 8;
56
- /** Persistent cooldown tracker — survives restarts, shared across modules. */
57
- const cooldownManager = TaskCooldownManager.getInstance();
58
- /**
59
- * Planner skip cache — tracks items the LLM planner reviewed and decided
60
- * weren't ready. Avoids re-calling the LLM (~2 min round-trip) for the same
61
- * unchanged items. Items are re-evaluated if their `updatedAt` changes or
62
- * after the cooldown expires.
63
- */
64
- const plannerSkipCache = new Map();
65
- const PLANNER_SKIP_COOLDOWN_MS = 2 * 60 * 60 * 1000; // 2 hours
66
- /**
67
- * Max agents the planner will allow before skipping a work check.
68
- * The planner can still spawn 1-3 agents per round, so actual peak may
69
- * briefly exceed this while a batch is starting up.
70
- */
71
- const MAX_CONCURRENT_AGENTS = parseInt(resolveEnv("MAX_CONCURRENT_AGENTS") ?? "", 10) || 3;
72
- /**
73
- * Map agent label → board taskId, so we can mark the board task as failed
74
- * when the agent fails and avoid immediately re-assigning it.
75
- */
76
- const labelToBoardTaskId = new Map();
77
- /**
78
- * Circuit breaker for LLM credit exhaustion.
79
- * When a credit error is detected, autonomous work pauses until the cooldown expires.
80
- * This prevents burning through the 15-min cycle repeatedly hitting a dead API.
81
- */
82
- let creditCircuitBreakerUntil = 0;
83
- const CREDIT_CIRCUIT_BREAKER_MS = 30 * 60_000; // 30 minutes
84
- // ─── Backlog Promotion ───────────────────────────────────────────────────────
85
- /**
86
- * Auto-promote "backlog" items to "todo" so the planner can pick them up.
87
- *
88
- * Promotion criteria:
89
- * - Has a description (spec exists, not just a title)
90
- * - Not on cooldown (don't promote items that keep failing)
91
- *
92
- * This closes the gap where items seeded into "backlog" state would sit
93
- * forever because the planner only picks up "todo" items.
94
- */
95
- async function promoteBacklogItems(store, tasks) {
96
- const backlog = tasks.filter((t) => t.state === "backlog" &&
97
- t.description && t.description.length > 0 &&
98
- !cooldownManager.shouldSkip(t.id));
99
- for (const task of backlog) {
100
- await store.update(task.id, { state: "todo" });
101
- task.state = "todo"; // Update in-place so the filter below sees it
102
- log.info(`Promoted ${task.identifier} from backlog → todo`, {
103
- identifier: task.identifier,
104
- title: task.title,
105
- });
106
- logActivity({
107
- source: "autonomous",
108
- summary: `Promoted ${task.identifier} from backlog → todo: "${task.title}"`,
109
- actionLabel: "AUTONOMOUS",
110
- reason: "backlog auto-promotion",
111
- });
112
- }
113
- }
114
- // ─── Public API ──────────────────────────────────────────────────────────────
115
- /**
116
- * Start the autonomous work timer. Idempotent.
117
- * First check fires 60s after boot (not immediately — gives system time to init).
118
- * Then repeats every 15 min.
119
- */
120
- export function startAutonomousTimer(intervalMs) {
121
- if (timer)
122
- return;
123
- const interval = intervalMs ?? AUTONOMOUS_INTERVAL_MS;
124
- // Initialize cooldown manager (loads persisted state from disk)
125
- cooldownManager.init().catch((err) => {
126
- log.warn(`Cooldown manager init failed: ${err instanceof Error ? err.message : String(err)}`);
127
- });
128
- // Fire first check shortly after boot
129
- firstCheckTimer = setTimeout(async () => {
130
- firstCheckTimer = null;
131
- log.info(` First work check starting...`);
132
- try {
133
- await checkForWork();
134
- }
135
- catch (err) {
136
- const msg = err instanceof Error ? err.message : String(err);
137
- log.error(` First check error: ${msg}`);
138
- logActivity({ source: "autonomous", summary: `First check error: ${msg}`, actionLabel: "AUTONOMOUS", reason: "15-min autonomous planner cycle" });
139
- }
140
- }, FIRST_CHECK_DELAY_MS);
141
- // Then repeat on interval
142
- timer = setInterval(async () => {
143
- try {
144
- await checkForWork();
145
- }
146
- catch (err) {
147
- const msg = err instanceof Error ? err.message : String(err);
148
- log.error(` Work check error: ${msg}`);
149
- logActivity({ source: "autonomous", summary: `Work check error: ${msg}`, actionLabel: "AUTONOMOUS", reason: "15-min autonomous planner cycle" });
150
- }
151
- }, interval);
152
- const mins = Math.round(interval / 60_000);
153
- log.info(`Autonomous work: first check in ${FIRST_CHECK_DELAY_MS / 1000}s, then every ${mins} min`);
154
- }
155
- /** Stop the autonomous timer. */
156
- export function stopAutonomousTimer() {
157
- if (firstCheckTimer) {
158
- clearTimeout(firstCheckTimer);
159
- firstCheckTimer = null;
160
- }
161
- if (timer) {
162
- clearInterval(timer);
163
- timer = null;
164
- }
165
- // Flush cooldown state to disk
166
- cooldownManager.shutdown();
167
- }
168
- /** Is the autonomous timer running? */
169
- export function isAutonomousTimerRunning() {
170
- return timer !== null;
171
- }
172
- /**
173
- * Called by PressureIntegrator when tension exceeds threshold.
174
- * Wraps checkForWork() with the existing planningInProgress guard.
175
- */
176
- export async function triggerPulse() {
177
- if (planningInProgress) {
178
- log.info(`Pulse trigger skipped: planning already in progress`);
179
- return;
180
- }
181
- if (activeAgentCount() >= MAX_CONCURRENT_AGENTS) {
182
- log.info(`Pulse trigger skipped: at capacity (${activeAgentCount()}/${MAX_CONCURRENT_AGENTS})`);
183
- return;
184
- }
185
- log.info(`Pulse trigger: checking for work...`);
186
- await checkForWork();
187
- }
188
- /** Get a snapshot of the autonomous work loop's current state. */
189
- export function getAutonomousStatus() {
190
- const activeSessions = [...continuationRounds.keys()];
191
- const activeCooldowns = cooldownManager.listActiveCooldowns().map((c) => ({
192
- taskId: c.taskId,
193
- label: c.label,
194
- failureCount: c.failureCount,
195
- remainingMin: Math.round(c.remainingMs / 60_000),
196
- }));
197
- const cbRemain = Math.max(0, creditCircuitBreakerUntil - Date.now());
198
- return {
199
- timerRunning: timer !== null,
200
- intervalMs: AUTONOMOUS_INTERVAL_MS,
201
- firstCheckDelayMs: FIRST_CHECK_DELAY_MS,
202
- creditCircuitBreakerActive: Date.now() < creditCircuitBreakerUntil,
203
- creditCircuitBreakerRemainingMin: Math.ceil(cbRemain / 60_000),
204
- planningInProgress,
205
- activeAgents: activeAgentCount(),
206
- maxConcurrentAgents: MAX_CONCURRENT_AGENTS,
207
- activeSessions,
208
- cooldowns: activeCooldowns,
209
- plannerSkippedItems: plannerSkipCache.size,
210
- pulse: getPressureIntegrator()?.getStatus() ?? null,
211
- };
212
- }
213
- /**
214
- * Called by onBatchComplete when all agents in a session finish.
215
- * Commits work, then checks if there's more to do.
216
- */
217
- export async function continueAfterBatch(sessionId, results) {
218
- // Commit agent work as a logical batch
219
- const round = (continuationRounds.get(sessionId) ?? 0) + 1;
220
- try {
221
- const commitResult = await commitAgentBatch(results, round);
222
- if (commitResult.ok && commitResult.message !== "No changes to commit" && commitResult.message !== "No staged changes to commit") {
223
- logActivity({ source: "system", summary: commitResult.message });
224
- }
225
- }
226
- catch (err) {
227
- logActivity({ source: "autonomous", summary: `Commit error: ${err instanceof Error ? err.message : String(err)}`, actionLabel: "AUTONOMOUS", reason: "batch continuation commit" });
228
- }
229
- // Record failed agent labels → board task cooldown (escalating, persisted)
230
- const batchFailedCount = results.filter((r) => r.status === "failed").length;
231
- for (const r of results) {
232
- if (r.status === "failed") {
233
- const boardTaskId = labelToBoardTaskId.get(r.label);
234
- if (boardTaskId) {
235
- cooldownManager.recordFailure(boardTaskId, r.label);
236
- const failureCount = cooldownManager.getFailureCount(boardTaskId);
237
- if (failureCount >= 3) {
238
- generateBridgeReport(boardTaskId, r.label, failureCount).catch(() => { });
239
- }
240
- labelToBoardTaskId.delete(r.label);
241
- }
242
- }
243
- }
244
- // Track cumulative failures across all rounds for this session
245
- if (batchFailedCount > 0) {
246
- const prevCumulative = sessionCumulativeFailures.get(sessionId) ?? 0;
247
- const newCumulative = prevCumulative + batchFailedCount;
248
- sessionCumulativeFailures.set(sessionId, newCumulative);
249
- if (newCumulative >= MAX_SESSION_CUMULATIVE_FAILURES) {
250
- log.info(` Session ${sessionId} hit ${newCumulative} cumulative failures (limit ${MAX_SESSION_CUMULATIVE_FAILURES}) — stopping`);
251
- logActivity({
252
- source: "autonomous",
253
- summary: `Session hit ${newCumulative} cumulative failures — stopping`,
254
- actionLabel: "AUTONOMOUS",
255
- reason: "cumulative failure limit reached",
256
- });
257
- continuationRounds.delete(sessionId);
258
- sessionCumulativeFailures.delete(sessionId);
259
- await reflectOnSession({ sessionId, round, results, isFinal: true }).catch(() => { });
260
- return;
261
- }
262
- }
263
- // If all agents in the batch failed, stop continuation — don't keep trying
264
- const allFailed = results.every((r) => r.status === "failed");
265
- if (allFailed) {
266
- log.info(` All ${results.length} agent(s) in batch failed — stopping continuation for session ${sessionId}`);
267
- logActivity({ source: "autonomous", summary: `All agents in batch failed — stopping continuation`, actionLabel: "AUTONOMOUS", reason: "all-failure batch halt" });
268
- continuationRounds.delete(sessionId);
269
- sessionCumulativeFailures.delete(sessionId);
270
- await reflectOnSession({ sessionId, round, results, isFinal: true }).catch(() => { });
271
- return;
272
- }
273
- // Check round limit
274
- if (round > MAX_CONTINUATION_ROUNDS) {
275
- log.info(` Hit ${MAX_CONTINUATION_ROUNDS}-round limit for session ${sessionId}, pausing`);
276
- logActivity({ source: "autonomous", summary: `Hit ${MAX_CONTINUATION_ROUNDS}-round limit, pausing`, actionLabel: "AUTONOMOUS", reason: "round limit reached" });
277
- continuationRounds.delete(sessionId);
278
- sessionCumulativeFailures.delete(sessionId);
279
- await reflectOnSession({ sessionId, round, results, isFinal: true }).catch(() => { });
280
- return;
281
- }
282
- continuationRounds.set(sessionId, round);
283
- const batchSummary = results.map((r) => `${r.label}: ${r.status}`).join(", ");
284
- // Fixed 2s pause between rounds — enough to prevent rapid cycling,
285
- // short enough to keep the cluster responsive. Guards (dedup, cooldown,
286
- // cumulative failure limit) handle runaway scenarios, not the delay.
287
- const continuationDelayMs = 2000;
288
- const msg = `Batch done (${batchSummary}). Checking for more work in ${Math.round(continuationDelayMs / 1000)}s (round ${round}/${MAX_CONTINUATION_ROUNDS})...`;
289
- log.info(` ${msg}`);
290
- logActivity({ source: "autonomous", summary: msg, actionLabel: "AUTONOMOUS", reason: `batch continuation round ${round}` });
291
- pushNotification({ timestamp: new Date().toISOString(), source: "autonomous", message: msg });
292
- // Build context from batch results
293
- const resultSummary = results
294
- .map((r) => `- ${r.label}: ${r.status}`)
295
- .join("\n");
296
- // Skip LLM-based reflection on intermediate rounds — saves 30-60s per round.
297
- // Final reflections still run at the early-exit points above (all-failed, cumulative limit, round limit).
298
- await new Promise((resolve) => setTimeout(resolve, continuationDelayMs));
299
- const enhancedContext = `Just completed round ${round}:\n${resultSummary}`;
300
- await planAndSpawn(sessionId, enhancedContext);
301
- }
302
- /** Reset continuation tracking for a session. */
303
- export function resetContinuation(sessionId) {
304
- continuationRounds.delete(sessionId);
305
- sessionCumulativeFailures.delete(sessionId);
306
- }
307
- // ─── Core ────────────────────────────────────────────────────────────────────
308
- /**
309
- * Periodic check: if agents are idle and there's actionable work, spawn agents.
310
- * Exported so PressureIntegrator can call it directly on pulse.
311
- */
312
- export async function checkForWork() {
313
- // Piggyback: check Resend inbox while we're already awake.
314
- // Debounced internally — cheap no-op if checked recently or not configured.
315
- checkResendInbox().catch(() => { });
316
- // Circuit breaker: skip if credits are exhausted (avoid hammering a dead API)
317
- if (Date.now() < creditCircuitBreakerUntil) {
318
- const remainMin = Math.ceil((creditCircuitBreakerUntil - Date.now()) / 60_000);
319
- log.info(` Credit circuit breaker active — skipping work check (${remainMin}min remaining)`);
320
- return;
321
- }
322
- const currentAgents = activeAgentCount();
323
- if (currentAgents >= MAX_CONCURRENT_AGENTS) {
324
- log.info(` At capacity (${currentAgents}/${MAX_CONCURRENT_AGENTS} agents), skipping work check`);
325
- return;
326
- }
327
- if (planningInProgress) {
328
- log.info(` Planning already in progress, skipping work check`);
329
- return;
330
- }
331
- const board = getBoardProvider();
332
- if (!board?.isAvailable()) {
333
- log.info(` No board provider available`);
334
- return;
335
- }
336
- const store = board.getStore?.();
337
- if (!store) {
338
- log.info(` No queue store available`);
339
- return;
340
- }
341
- const allTasks = await store.list();
342
- const atCapacity = currentAgents >= MAX_CONCURRENT_AGENTS;
343
- // Auto-promote backlog items to todo so they become actionable
344
- await promoteBacklogItems(store, allTasks);
345
- // Filter to tasks the planner can assign.
346
- // "todo" tasks assigned to the agent are reclaimed if no agent is actively running —
347
- // they're leftovers from a previous session that failed or was interrupted.
348
- const agentAssignee = `${getInstanceNameLower()}-agent`;
349
- const actionable = allTasks.filter((t) => {
350
- if (t.state !== "todo")
351
- return false;
352
- if (t.assignee && !(t.assignee === agentAssignee && t.state === "todo" && !atCapacity))
353
- return false;
354
- if (cooldownManager.shouldSkip(t.id))
355
- return false;
356
- return true;
357
- });
358
- if (actionable.length === 0) {
359
- log.info(` No actionable items on the board`);
360
- return;
361
- }
362
- // Filter out items the planner already reviewed and skipped (unless updated since)
363
- const now = Date.now();
364
- const needsReview = actionable.filter((t) => {
365
- const cached = plannerSkipCache.get(t.id);
366
- if (!cached)
367
- return true;
368
- // Re-evaluate if item was updated after the planner skipped it
369
- if (t.updatedAt && t.updatedAt !== cached.itemUpdatedAt)
370
- return true;
371
- // Re-evaluate if the skip cooldown expired
372
- if (now - cached.skippedAt >= PLANNER_SKIP_COOLDOWN_MS) {
373
- plannerSkipCache.delete(t.id);
374
- return true;
375
- }
376
- return false;
377
- });
378
- if (needsReview.length === 0) {
379
- log.info(` ${actionable.length} actionable item(s) all recently reviewed by planner — skipping LLM call`);
380
- return;
381
- }
382
- log.info(` Found ${needsReview.length} actionable item(s) (${actionable.length - needsReview.length} cached skip), calling LLM planner...`);
383
- // Create an internal session for batch tracking
384
- autonomousSessionCounter++;
385
- const sessionId = `auto-${Date.now()}-${autonomousSessionCounter}`;
386
- await planAndSpawn(sessionId, null, needsReview);
387
- }
388
- /**
389
- * Ask the LLM what to work on, parse AGENT_REQUEST blocks, spawn agents.
390
- */
391
- async function planAndSpawn(sessionId, priorContext, explicitItems) {
392
- if (planningInProgress) {
393
- log.info(` planAndSpawn already running, skipping duplicate call`);
394
- return;
395
- }
396
- planningInProgress = true;
397
- try {
398
- await planAndSpawnInner(sessionId, priorContext, explicitItems);
399
- }
400
- finally {
401
- planningInProgress = false;
402
- }
403
- }
404
- async function planAndSpawnInner(sessionId, priorContext, explicitItems) {
405
- // Get actionable board items (unless explicitly provided)
406
- let actionable = explicitItems;
407
- if (!actionable) {
408
- const board = getBoardProvider();
409
- if (!board?.isAvailable())
410
- return;
411
- const store = board.getStore?.();
412
- if (!store)
413
- return;
414
- const allTasks = await store.list();
415
- const atCap = activeAgentCount() >= MAX_CONCURRENT_AGENTS;
416
- // Auto-promote backlog items to todo so they become actionable
417
- await promoteBacklogItems(store, allTasks);
418
- const agentAssigneeInner = `${getInstanceNameLower()}-agent`;
419
- actionable = allTasks.filter((t) => {
420
- if (t.state !== "todo")
421
- return false;
422
- if (t.assignee && !(t.assignee === agentAssigneeInner && t.state === "todo" && !atCap))
423
- return false;
424
- if (cooldownManager.shouldSkip(t.id))
425
- return false;
426
- return true;
427
- });
428
- if (actionable.length === 0) {
429
- log.info(` No more actionable items — pausing`);
430
- logActivity({ source: "autonomous", summary: "No more actionable items — pausing", actionLabel: "AUTONOMOUS", reason: "planner found no work" });
431
- continuationRounds.delete(sessionId);
432
- sessionCumulativeFailures.delete(sessionId);
433
- return;
434
- }
435
- }
436
- // Build board context with descriptions and project names
437
- const boardContext = actionable
438
- .map((t) => {
439
- const desc = t.description ? `\n ${t.description.slice(0, 1500)}` : "";
440
- const proj = t.project ? ` {${t.project}}` : "";
441
- return `- **${t.identifier}**: ${t.title} [${t.state}, P${t.priority ?? 4}]${proj} (id: ${t.id})${desc}`;
442
- })
443
- .join("\n");
444
- // Build the planning prompt
445
- const prompt = await buildPlannerPrompt(boardContext, priorContext);
446
- const messages = [
447
- { role: "system", content: getPlannerSystemPrompt() },
448
- { role: "user", content: prompt },
449
- ];
450
- try {
451
- log.info(` Calling LLM planner (model: ${PLANNER_MODEL}, provider: ${resolveProvider()})...`);
452
- const response = await completeChat({
453
- messages,
454
- model: PLANNER_MODEL,
455
- provider: resolveProvider(),
456
- });
457
- log.info(` LLM response (${response.length} chars): ${response.slice(0, 200)}...`);
458
- // Process action blocks + meta blocks (TASK_DONE, etc.) via capability registry
459
- let actionsExecuted = 0;
460
- {
461
- const capReg = getCapabilityRegistry();
462
- if (capReg) {
463
- const { results, metaResults } = await capReg.processResponse(response, { origin: "autonomous" });
464
- actionsExecuted += results.filter((r) => r.ok).length;
465
- actionsExecuted += metaResults.reduce((n, m) => n + m.results.filter((r) => r.ok).length, 0);
466
- }
467
- }
468
- // Parse AGENT_REQUEST blocks from LLM response
469
- const blocks = [...response.matchAll(/\[AGENT_REQUEST\]\s*([\s\S]*?)\s*\[\/AGENT_REQUEST\]/g)];
470
- if (blocks.length === 0 && actionsExecuted === 0) {
471
- // Extract a brief reason from the LLM response (strip markdown noise)
472
- const briefReason = response.replace(/[#*`\[\]]/g, "").trim().slice(0, 150);
473
- log.info(` LLM decided no agents needed. Response: ${response.slice(0, 300)}`);
474
- logActivity({
475
- source: "autonomous",
476
- summary: `Reviewed ${actionable.length} backlog item(s), none ready: ${briefReason}`,
477
- actionLabel: "AUTONOMOUS",
478
- reason: "planner decided no action needed",
479
- });
480
- // Cache these items so the next cycle skips the LLM call for unchanged items
481
- const skipTime = Date.now();
482
- for (const t of actionable) {
483
- plannerSkipCache.set(t.id, { skippedAt: skipTime, itemUpdatedAt: t.updatedAt });
484
- }
485
- log.info(` Cached ${actionable.length} item(s) as planner-skipped for ${PLANNER_SKIP_COOLDOWN_MS / 60_000}min`);
486
- continuationRounds.delete(sessionId);
487
- sessionCumulativeFailures.delete(sessionId);
488
- return;
489
- }
490
- if (blocks.length === 0 && actionsExecuted > 0) {
491
- log.info(` Executed ${actionsExecuted} action(s), no agents spawned`);
492
- logActivity({
493
- source: "autonomous",
494
- summary: `Executed ${actionsExecuted} action(s) from ${actionable.length} backlog item(s)`,
495
- actionLabel: "AUTONOMOUS",
496
- reason: "planner executed actions directly",
497
- });
498
- continuationRounds.delete(sessionId);
499
- sessionCumulativeFailures.delete(sessionId);
500
- return;
501
- }
502
- // Log the planning entry now that the LLM has actually decided to spawn
503
- const planEntry = logActivity({
504
- source: "autonomous",
505
- summary: `Planning ${blocks.length} agent(s) from ${actionable.length} backlog item(s)`,
506
- actionLabel: "AUTONOMOUS",
507
- reason: "15-min autonomous planner cycle",
508
- });
509
- const planTraceId = planEntry.traceId;
510
- log.info(` Found ${blocks.length} AGENT_REQUEST block(s)`);
511
- let spawnCount = 0;
512
- const spawnedLabels = [];
513
- for (const block of blocks) {
514
- if (spawnCount >= MAX_AGENTS_PER_ROUND)
515
- break;
516
- const rawContent = block[1].trim();
517
- let jsonStr = rawContent
518
- .replace(/^`{3,}(?:json)?\s*/i, "")
519
- .replace(/\s*`{3,}$/i, "")
520
- .replace(/^`+|`+$/g, "");
521
- const jsonMatch = jsonStr.match(/\{[\s\S]*\}/);
522
- if (!jsonMatch) {
523
- log.warn(` No JSON in AGENT_REQUEST block: ${rawContent.slice(0, 200)}`);
524
- logActivity({ source: "autonomous", summary: `No JSON in AGENT_REQUEST block`, actionLabel: "AUTONOMOUS", reason: "planner output parse issue" });
525
- continue;
526
- }
527
- try {
528
- const req = JSON.parse(jsonMatch[0]);
529
- if (!req.prompt) {
530
- log.warn(` AGENT_REQUEST missing "prompt" field`);
531
- continue;
532
- }
533
- const label = req.label || req.prompt.slice(0, 60);
534
- let finalPrompt = req.prompt;
535
- // Guard: dedup — skip if same work is already in progress or recently done
536
- const dedup = await checkDedup(label, finalPrompt);
537
- if (dedup.blocked) {
538
- log.info(` Dedup guard blocked: "${label}" — ${dedup.reason}`);
539
- recordDedupBlock(dedup.reason ?? "unknown");
540
- logActivity({
541
- source: "autonomous",
542
- summary: `Skipped "${label}": ${dedup.reason}`,
543
- actionLabel: "AUTONOMOUS",
544
- reason: "dedup guard",
545
- });
546
- continue;
547
- }
548
- // Guard: detect vague prompts
549
- const hasFilePath = /(?:src\/|brain\/|public\/|\.ts|\.js|\.md|\.json|\.yaml|\.yml)/.test(finalPrompt);
550
- const isVague = /\b(?:comprehensive|robust|production-ready|enterprise|scalable|world-class)\b/i.test(finalPrompt)
551
- && !hasFilePath;
552
- if (isVague) {
553
- finalPrompt = [
554
- `IMPORTANT: The original request is vague. Do NOT try to build everything listed.`,
555
- `Instead: 1) Read the existing codebase (start with src/ and package.json).`,
556
- `2) Pick ONE small, concrete piece you can implement that connects to existing code.`,
557
- `3) Build that one thing well.`,
558
- `4) If nothing concrete can be built without more requirements, create a brief spec at brain/knowledge/notes/.`,
559
- ``,
560
- `Original request:`,
561
- finalPrompt,
562
- ].join("\n");
563
- }
564
- // Track label→boardTaskId so we can cooldown on failure
565
- if (req.taskId) {
566
- labelToBoardTaskId.set(label, req.taskId);
567
- }
568
- spawnCount++;
569
- spawnedLabels.push(label);
570
- recordAutonomousAction();
571
- log.info(` Spawning agent: ${label}`);
572
- logActivity({ source: "autonomous", summary: `Spawning: ${label}`, backref: planTraceId, actionLabel: "AUTONOMOUS", reason: "planner selected from backlog" });
573
- await submitTask({
574
- label,
575
- prompt: finalPrompt,
576
- origin: "ai",
577
- sessionId,
578
- boardTaskId: req.taskId,
579
- readOnly: true, // Autonomous agents investigate and report only — no file edits
580
- });
581
- }
582
- catch (err) {
583
- log.error(` AGENT_REQUEST parse error: ${err instanceof Error ? err.message : String(err)}`);
584
- logActivity({
585
- source: "autonomous",
586
- summary: `AGENT_REQUEST parse error: ${err instanceof Error ? err.message : String(err)}`,
587
- actionLabel: "AUTONOMOUS",
588
- reason: "planner output parse error",
589
- });
590
- }
591
- }
592
- if (spawnCount > 0) {
593
- const summary = `Working on ${spawnCount} task(s): ${spawnedLabels.join(", ")}`;
594
- log.info(` ${summary}`);
595
- logActivity({ source: "autonomous", summary, backref: planTraceId, actionLabel: "AUTONOMOUS", reason: "planner selected from backlog" });
596
- pushNotification({
597
- timestamp: new Date().toISOString(),
598
- source: "autonomous",
599
- message: summary,
600
- });
601
- }
602
- else {
603
- log.warn(` Found ${blocks.length} block(s) but spawned 0 agents`);
604
- logActivity({ source: "autonomous", summary: "Parsed blocks but spawned 0 agents", actionLabel: "AUTONOMOUS", reason: "planner blocks had no valid agents" });
605
- continuationRounds.delete(sessionId);
606
- sessionCumulativeFailures.delete(sessionId);
607
- }
608
- }
609
- catch (err) {
610
- const msg = err instanceof Error ? err.message : String(err);
611
- log.error(` LLM call failed: ${msg}`);
612
- logActivity({ source: "autonomous", summary: `LLM call failed: ${msg}`, actionLabel: "AUTONOMOUS", reason: "15-min autonomous planner cycle" });
613
- // Trip circuit breaker on credit/billing errors to stop hammering a dead API
614
- const isCreditsIssue = (err instanceof LLMError && err.isCreditsError)
615
- || /402|credits|afford|payment.required|billing/i.test(msg);
616
- if (isCreditsIssue) {
617
- creditCircuitBreakerUntil = Date.now() + CREDIT_CIRCUIT_BREAKER_MS;
618
- const breaker_msg = `API credits exhausted — autonomous work paused for ${CREDIT_CIRCUIT_BREAKER_MS / 60_000}min. Top up at openrouter.ai/settings/credits.`;
619
- log.warn(breaker_msg);
620
- logActivity({ source: "autonomous", summary: breaker_msg, actionLabel: "AUTONOMOUS", reason: "credit circuit breaker tripped" });
621
- pushNotification({
622
- timestamp: new Date().toISOString(),
623
- source: "autonomous",
624
- message: breaker_msg,
625
- });
626
- }
627
- continuationRounds.delete(sessionId);
628
- sessionCumulativeFailures.delete(sessionId);
629
- }
630
- }
631
- // ─── Prompts ─────────────────────────────────────────────────────────────────
632
- function getPlannerSystemPrompt() {
633
- const capInstructions = getCapabilityRegistry()?.getPromptInstructions({ origin: "autonomous" }) ?? "";
634
- return `You are ${getInstanceName()}'s autonomous work planner. Your job is to look at the backlog and decide what agents should work on next.
635
-
636
- Rules:
637
- - Pick 1-3 items from the backlog that are ready to implement NOW.
638
- - Skip items that are vague specs or need human decisions — those need grooming first.
639
- - Prefer items with clear titles and descriptions over ambiguous ones.
640
- - Each agent gets ONE focused task. Don't bundle multiple items into one agent.
641
- - Write clear, specific agent prompts with file paths when possible.
642
- - If nothing is ready to implement, output NO [AGENT_REQUEST] blocks and explain why.
643
- - DO NOT retry tasks that have already been attempted and failed. If the "Recent agent history" section shows a task was tried before, skip it unless you have a fundamentally different approach (not just "try again").
644
- - If the same board item has failed multiple times, it probably needs human input or a spec change — skip it.
645
-
646
- ## Two types of actions
647
-
648
- ### 1. Code agents (for code/file tasks)
649
- For tasks that require reading/writing code or files, output:
650
-
651
- [AGENT_REQUEST]
652
- {
653
- "label": "Short descriptive name",
654
- "prompt": "Detailed instructions for the agent. Reference specific files. Be concrete.",
655
- "taskId": "the-task-id-from-the-board"
656
- }
657
- [/AGENT_REQUEST]
658
-
659
- The agent runs \`claude --print --dangerously-skip-permissions\` in the project root.
660
- It can read/write files, run npm commands, etc. Give it everything it needs to succeed independently.
661
- After making changes, the agent should run \`npm run build\` to verify compilation.
662
-
663
- ### 2. Google Workspace actions (for calendar, email, docs tasks)
664
- For tasks like scheduling meetings, sending emails, or creating documents, output action blocks directly — NO agent needed.
665
-
666
- ${capInstructions}
667
-
668
- IMPORTANT: When a task says "schedule", "meeting", "calendar", "appointment" — use [CALENDAR_ACTION], not an agent.
669
- When a task says "email", "send", "notify" — use [EMAIL_ACTION], not an agent.
670
- After outputting the action block, also include the taskId so the task can be marked done:
671
- [TASK_DONE]{"taskId": "the-task-id"}[/TASK_DONE]`;
672
- }
673
- async function buildPlannerPrompt(boardContext, priorContext) {
674
- const parts = [];
675
- parts.push(`Current time: ${new Date().toISOString()}`);
676
- parts.push(``);
677
- if (priorContext) {
678
- parts.push(`## Previous batch results`);
679
- parts.push(priorContext);
680
- parts.push(``);
681
- }
682
- // Include recent agent history so the planner knows what's been tried
683
- const historyContext = await buildAgentHistoryContext();
684
- if (historyContext) {
685
- parts.push(`## Recent agent history (last 24h)`);
686
- parts.push(historyContext);
687
- parts.push(``);
688
- }
689
- // Include currently cooling-down tasks
690
- const cooldownContext = cooldownManager.getCooldownContext();
691
- if (cooldownContext) {
692
- parts.push(`## Tasks on cooldown (DO NOT assign these)`);
693
- parts.push(cooldownContext);
694
- parts.push(``);
695
- }
696
- parts.push(`## Actionable backlog items (unassigned, ready to work)`);
697
- parts.push(boardContext);
698
- parts.push(``);
699
- parts.push(`Review these items and decide which ones to assign to agents. Output [AGENT_REQUEST] blocks for each.`);
700
- parts.push(`If an item is too vague or needs human input, skip it and explain why.`);
701
- return parts.join("\n");
702
- }
703
- /**
704
- * Build a summary of recent agent task results so the planner can avoid
705
- * re-attempting tasks that have already been tried and failed.
706
- *
707
- * Optimization: uses `since` filter to skip reading old task files from disk
708
- * entirely (filename-embedded timestamps are checked before file read).
709
- */
710
- async function buildAgentHistoryContext() {
711
- try {
712
- const cutoff = Date.now() - 24 * 60 * 60 * 1000; // last 24h
713
- const tasks = await listAgentTasks({ since: cutoff });
714
- const recent = tasks.filter((t) => {
715
- const ts = t.finishedAt || t.createdAt;
716
- return ts && new Date(ts).getTime() > cutoff;
717
- });
718
- if (recent.length === 0)
719
- return null;
720
- // Group by normalized label to show patterns
721
- const byLabel = new Map();
722
- for (const t of recent) {
723
- const key = t.label.replace(/\d+/g, "N").slice(0, 60);
724
- if (!byLabel.has(key))
725
- byLabel.set(key, { completed: 0, failed: 0, labels: [] });
726
- const entry = byLabel.get(key);
727
- if (t.status === "completed")
728
- entry.completed++;
729
- else if (t.status === "failed")
730
- entry.failed++;
731
- if (!entry.labels.includes(t.label))
732
- entry.labels.push(t.label);
733
- }
734
- const lines = [];
735
- for (const [key, info] of byLabel) {
736
- const status = [];
737
- if (info.completed > 0)
738
- status.push(`${info.completed} completed`);
739
- if (info.failed > 0)
740
- status.push(`${info.failed} FAILED`);
741
- lines.push(`- "${info.labels[0]}": ${status.join(", ")}`);
742
- }
743
- return lines.join("\n");
744
- }
745
- catch {
746
- return null;
747
- }
748
- }
749
- //# sourceMappingURL=autonomous.js.map