@swarmclawai/swarmclaw 1.2.0 → 1.2.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 (241) hide show
  1. package/README.md +19 -0
  2. package/package.json +5 -2
  3. package/skills/coding-agent/SKILL.md +111 -0
  4. package/skills/github/SKILL.md +140 -0
  5. package/skills/nano-banana-pro/SKILL.md +62 -0
  6. package/skills/nano-banana-pro/scripts/generate_image.py +235 -0
  7. package/skills/nano-pdf/SKILL.md +53 -0
  8. package/skills/openai-image-gen/SKILL.md +78 -0
  9. package/skills/openai-image-gen/scripts/gen.py +328 -0
  10. package/skills/resourceful-problem-solving/SKILL.md +49 -0
  11. package/skills/skill-creator/SKILL.md +147 -0
  12. package/skills/skill-creator/scripts/init_skill.py +378 -0
  13. package/skills/skill-creator/scripts/quick_validate.py +159 -0
  14. package/skills/summarize/SKILL.md +77 -0
  15. package/src/app/api/auth/route.ts +20 -5
  16. package/src/app/api/chats/[id]/deploy/route.ts +11 -6
  17. package/src/app/api/chats/[id]/devserver/route.ts +17 -20
  18. package/src/app/api/chats/[id]/messages/route.ts +15 -11
  19. package/src/app/api/chats/[id]/route.ts +9 -10
  20. package/src/app/api/chats/[id]/stop/route.ts +5 -7
  21. package/src/app/api/chats/messages-route.test.ts +8 -6
  22. package/src/app/api/chats/route.ts +9 -10
  23. package/src/app/api/credentials/[id]/route.ts +4 -1
  24. package/src/app/api/extensions/marketplace/route.ts +5 -2
  25. package/src/app/api/ip/route.ts +2 -2
  26. package/src/app/api/memory/maintenance/route.ts +5 -2
  27. package/src/app/api/preview-server/route.ts +15 -12
  28. package/src/app/api/projects/[id]/route.ts +7 -46
  29. package/src/app/api/system/status/route.ts +11 -0
  30. package/src/app/api/upload/route.ts +4 -1
  31. package/src/cli/index.js +7 -0
  32. package/src/cli/spec.js +1 -0
  33. package/src/components/agents/agent-files-editor.tsx +44 -32
  34. package/src/components/agents/personality-builder.tsx +13 -7
  35. package/src/components/agents/trash-list.tsx +1 -1
  36. package/src/components/chat/chat-area.tsx +45 -23
  37. package/src/components/chat/message-bubble.test.ts +35 -0
  38. package/src/components/chat/message-bubble.tsx +20 -9
  39. package/src/components/chat/message-list.tsx +62 -42
  40. package/src/components/chat/swarm-status-card.tsx +10 -3
  41. package/src/components/input/chat-input.tsx +34 -14
  42. package/src/components/layout/daemon-indicator.tsx +7 -8
  43. package/src/components/layout/update-banner.tsx +8 -13
  44. package/src/components/logs/log-list.tsx +1 -1
  45. package/src/components/memory/memory-card.tsx +3 -1
  46. package/src/components/org-chart/org-chart-view.tsx +4 -0
  47. package/src/components/projects/project-list.tsx +4 -2
  48. package/src/components/projects/tabs/overview-tab.tsx +3 -2
  49. package/src/components/secrets/secret-sheet.tsx +1 -1
  50. package/src/components/secrets/secrets-list.tsx +1 -1
  51. package/src/components/shared/agent-switch-dialog.tsx +12 -6
  52. package/src/components/shared/dir-browser.tsx +22 -18
  53. package/src/components/skills/skill-sheet.tsx +2 -3
  54. package/src/components/tasks/task-list.tsx +1 -1
  55. package/src/components/tasks/task-sheet.tsx +1 -1
  56. package/src/hooks/use-openclaw-gateway.ts +46 -27
  57. package/src/instrumentation.ts +10 -7
  58. package/src/lib/chat/assistant-render-id.ts +3 -0
  59. package/src/lib/chat/chat-streaming-state.test.ts +42 -3
  60. package/src/lib/chat/chat-streaming-state.ts +20 -8
  61. package/src/lib/chat/chat.ts +18 -2
  62. package/src/lib/chat/queued-message-queue.test.ts +23 -1
  63. package/src/lib/chat/queued-message-queue.ts +11 -2
  64. package/src/lib/providers/anthropic.ts +6 -3
  65. package/src/lib/providers/claude-cli.ts +9 -3
  66. package/src/lib/providers/cli-utils.test.ts +124 -0
  67. package/src/lib/providers/cli-utils.ts +15 -0
  68. package/src/lib/providers/codex-cli.ts +9 -3
  69. package/src/lib/providers/gemini-cli.ts +6 -2
  70. package/src/lib/providers/index.ts +4 -1
  71. package/src/lib/providers/ollama.ts +5 -2
  72. package/src/lib/providers/openai.ts +8 -5
  73. package/src/lib/providers/opencode-cli.ts +6 -2
  74. package/src/lib/server/activity/activity-log.ts +21 -0
  75. package/src/lib/server/agents/agent-availability.test.ts +10 -5
  76. package/src/lib/server/agents/agent-cascade.ts +79 -59
  77. package/src/lib/server/agents/agent-registry.ts +23 -4
  78. package/src/lib/server/agents/agent-repository.ts +90 -0
  79. package/src/lib/server/agents/delegation-job-repository.ts +53 -0
  80. package/src/lib/server/agents/delegation-jobs.ts +11 -4
  81. package/src/lib/server/agents/guardian-checkpoint-repository.ts +35 -0
  82. package/src/lib/server/agents/guardian.ts +2 -2
  83. package/src/lib/server/agents/main-agent-loop.ts +14 -6
  84. package/src/lib/server/agents/main-loop-state-repository.ts +38 -0
  85. package/src/lib/server/agents/subagent-runtime.ts +9 -6
  86. package/src/lib/server/agents/subagent-swarm.ts +3 -2
  87. package/src/lib/server/agents/task-session.ts +3 -4
  88. package/src/lib/server/approvals/approval-repository.ts +30 -0
  89. package/src/lib/server/autonomy/supervisor-incident-repository.ts +42 -0
  90. package/src/lib/server/autonomy/supervisor-reflection.ts +14 -1
  91. package/src/lib/server/chat-execution/chat-execution-types.ts +38 -0
  92. package/src/lib/server/chat-execution/chat-execution-utils.ts +1 -1
  93. package/src/lib/server/chat-execution/chat-execution.ts +84 -1914
  94. package/src/lib/server/chat-execution/chat-turn-finalization.ts +620 -0
  95. package/src/lib/server/chat-execution/chat-turn-partial-persistence.ts +221 -0
  96. package/src/lib/server/chat-execution/chat-turn-preflight.ts +133 -0
  97. package/src/lib/server/chat-execution/chat-turn-preparation.ts +817 -0
  98. package/src/lib/server/chat-execution/chat-turn-stream-execution.ts +296 -0
  99. package/src/lib/server/chat-execution/chat-turn-tool-routing.ts +5 -5
  100. package/src/lib/server/chat-execution/continuation-evaluator.ts +4 -3
  101. package/src/lib/server/chat-execution/continuation-limits.ts +6 -3
  102. package/src/lib/server/chat-execution/message-classifier.test.ts +329 -0
  103. package/src/lib/server/chat-execution/message-classifier.ts +5 -2
  104. package/src/lib/server/chat-execution/post-stream-finalization.ts +5 -2
  105. package/src/lib/server/chat-execution/prompt-builder.ts +22 -1
  106. package/src/lib/server/chat-execution/prompt-sections.ts +55 -13
  107. package/src/lib/server/chat-execution/response-completeness.ts +5 -2
  108. package/src/lib/server/chat-execution/situational-awareness.ts +12 -7
  109. package/src/lib/server/chat-execution/stream-agent-chat.ts +58 -25
  110. package/src/lib/server/chatrooms/chatroom-memory-bridge.ts +6 -3
  111. package/src/lib/server/chatrooms/chatroom-repository.ts +32 -0
  112. package/src/lib/server/connectors/bluebubbles.ts +7 -4
  113. package/src/lib/server/connectors/connector-inbound.ts +16 -13
  114. package/src/lib/server/connectors/connector-lifecycle.ts +11 -8
  115. package/src/lib/server/connectors/connector-outbound.ts +6 -3
  116. package/src/lib/server/connectors/connector-repository.ts +58 -0
  117. package/src/lib/server/connectors/discord.ts +10 -7
  118. package/src/lib/server/connectors/email.ts +17 -14
  119. package/src/lib/server/connectors/googlechat.ts +7 -4
  120. package/src/lib/server/connectors/inbound-audio-transcription.ts +5 -2
  121. package/src/lib/server/connectors/matrix.ts +6 -3
  122. package/src/lib/server/connectors/openclaw.ts +20 -17
  123. package/src/lib/server/connectors/outbox.ts +4 -1
  124. package/src/lib/server/connectors/runtime-state.test.ts +117 -0
  125. package/src/lib/server/connectors/runtime-state.ts +19 -0
  126. package/src/lib/server/connectors/session-consolidation.ts +5 -2
  127. package/src/lib/server/connectors/signal.ts +9 -6
  128. package/src/lib/server/connectors/slack.ts +13 -10
  129. package/src/lib/server/connectors/teams.ts +8 -5
  130. package/src/lib/server/connectors/telegram.ts +15 -12
  131. package/src/lib/server/connectors/whatsapp.ts +32 -29
  132. package/src/lib/server/credentials/credential-repository.ts +7 -0
  133. package/src/lib/server/embeddings.ts +4 -1
  134. package/src/lib/server/gateways/gateway-profile-repository.ts +4 -0
  135. package/src/lib/server/link-understanding.ts +4 -1
  136. package/src/lib/server/memory/memory-abstract.test.ts +59 -0
  137. package/src/lib/server/memory/memory-abstract.ts +59 -0
  138. package/src/lib/server/memory/memory-db.ts +40 -14
  139. package/src/lib/server/missions/mission-repository.ts +74 -0
  140. package/src/lib/server/missions/mission-service/actions.ts +6 -0
  141. package/src/lib/server/missions/mission-service/bindings.ts +9 -0
  142. package/src/lib/server/missions/mission-service/context.ts +4 -0
  143. package/src/lib/server/missions/mission-service/core.ts +2269 -0
  144. package/src/lib/server/missions/mission-service/queries.ts +12 -0
  145. package/src/lib/server/missions/mission-service/recovery.ts +5 -0
  146. package/src/lib/server/missions/mission-service/ticks.ts +9 -0
  147. package/src/lib/server/missions/mission-service.test.ts +9 -2
  148. package/src/lib/server/missions/mission-service.ts +6 -2263
  149. package/src/lib/server/openclaw/gateway.ts +8 -5
  150. package/src/lib/server/persistence/repository-utils.ts +154 -0
  151. package/src/lib/server/persistence/storage-context.ts +51 -0
  152. package/src/lib/server/persistence/transaction.ts +1 -0
  153. package/src/lib/server/project-utils.ts +13 -0
  154. package/src/lib/server/projects/project-repository.ts +36 -0
  155. package/src/lib/server/projects/project-service.ts +79 -0
  156. package/src/lib/server/protocols/protocol-agent-turn.ts +5 -2
  157. package/src/lib/server/protocols/protocol-normalization.test.ts +6 -4
  158. package/src/lib/server/protocols/protocol-run-lifecycle.ts +5 -2
  159. package/src/lib/server/protocols/protocol-step-helpers.ts +4 -1
  160. package/src/lib/server/provider-health.ts +18 -0
  161. package/src/lib/server/query-expansion.ts +4 -1
  162. package/src/lib/server/runtime/alert-dispatch.ts +8 -7
  163. package/src/lib/server/runtime/daemon-policy.ts +1 -1
  164. package/src/lib/server/runtime/daemon-state/core.ts +1570 -0
  165. package/src/lib/server/runtime/daemon-state/health.ts +6 -0
  166. package/src/lib/server/runtime/daemon-state/policy.ts +7 -0
  167. package/src/lib/server/runtime/daemon-state/supervisor.ts +6 -0
  168. package/src/lib/server/runtime/daemon-state.test.ts +48 -0
  169. package/src/lib/server/runtime/daemon-state.ts +3 -1331
  170. package/src/lib/server/runtime/estop-repository.ts +4 -0
  171. package/src/lib/server/runtime/estop.ts +3 -1
  172. package/src/lib/server/runtime/heartbeat-service.test.ts +2 -2
  173. package/src/lib/server/runtime/heartbeat-service.ts +78 -34
  174. package/src/lib/server/runtime/heartbeat-wake.ts +6 -4
  175. package/src/lib/server/runtime/idle-window.ts +6 -3
  176. package/src/lib/server/runtime/network.ts +11 -0
  177. package/src/lib/server/runtime/orchestrator-events.ts +2 -2
  178. package/src/lib/server/runtime/perf.ts +4 -1
  179. package/src/lib/server/runtime/process-manager.ts +7 -4
  180. package/src/lib/server/runtime/queue/claims.ts +4 -0
  181. package/src/lib/server/runtime/queue/core.ts +2079 -0
  182. package/src/lib/server/runtime/queue/execution.ts +7 -0
  183. package/src/lib/server/runtime/queue/followups.ts +4 -0
  184. package/src/lib/server/runtime/queue/queries.ts +12 -0
  185. package/src/lib/server/runtime/queue/recovery.ts +7 -0
  186. package/src/lib/server/runtime/queue-recovery.test.ts +48 -13
  187. package/src/lib/server/runtime/queue-repository.ts +17 -0
  188. package/src/lib/server/runtime/queue.ts +5 -2058
  189. package/src/lib/server/runtime/run-ledger.ts +6 -5
  190. package/src/lib/server/runtime/run-repository.ts +73 -0
  191. package/src/lib/server/runtime/runtime-lock-repository.ts +8 -0
  192. package/src/lib/server/runtime/runtime-settings.ts +1 -1
  193. package/src/lib/server/runtime/runtime-state.ts +99 -0
  194. package/src/lib/server/runtime/scheduler.ts +13 -8
  195. package/src/lib/server/runtime/session-run-manager/cancellation.ts +157 -0
  196. package/src/lib/server/runtime/session-run-manager/drain.ts +246 -0
  197. package/src/lib/server/runtime/session-run-manager/enqueue.ts +287 -0
  198. package/src/lib/server/runtime/session-run-manager/queries.ts +117 -0
  199. package/src/lib/server/runtime/session-run-manager/recovery.ts +238 -0
  200. package/src/lib/server/runtime/session-run-manager/state.ts +441 -0
  201. package/src/lib/server/runtime/session-run-manager/types.ts +74 -0
  202. package/src/lib/server/runtime/session-run-manager.ts +72 -1374
  203. package/src/lib/server/runtime/watch-job-repository.ts +35 -0
  204. package/src/lib/server/runtime/watch-jobs.ts +3 -1
  205. package/src/lib/server/sandbox/bridge-auth-registry.ts +6 -0
  206. package/src/lib/server/sandbox/novnc-auth.ts +10 -0
  207. package/src/lib/server/schedules/schedule-repository.ts +42 -0
  208. package/src/lib/server/session-tools/context.ts +14 -0
  209. package/src/lib/server/session-tools/discovery.ts +9 -6
  210. package/src/lib/server/session-tools/index.ts +3 -1
  211. package/src/lib/server/session-tools/platform.ts +1 -1
  212. package/src/lib/server/session-tools/subagent.ts +23 -2
  213. package/src/lib/server/session-tools/wallet.ts +4 -1
  214. package/src/lib/server/sessions/session-repository.ts +85 -0
  215. package/src/lib/server/settings/settings-repository.ts +25 -0
  216. package/src/lib/server/skills/clawhub-client.ts +4 -1
  217. package/src/lib/server/skills/runtime-skill-resolver.ts +8 -2
  218. package/src/lib/server/skills/skill-discovery.test.ts +2 -2
  219. package/src/lib/server/skills/skill-discovery.ts +2 -2
  220. package/src/lib/server/skills/skill-eligibility.ts +6 -0
  221. package/src/lib/server/skills/skill-repository.ts +14 -0
  222. package/src/lib/server/solana.ts +6 -0
  223. package/src/lib/server/storage-auth.ts +5 -5
  224. package/src/lib/server/storage-normalization.ts +4 -0
  225. package/src/lib/server/storage.ts +32 -32
  226. package/src/lib/server/tasks/task-followups.ts +4 -1
  227. package/src/lib/server/tasks/task-repository.ts +54 -0
  228. package/src/lib/server/tool-loop-detection.ts +8 -3
  229. package/src/lib/server/tool-planning.ts +226 -0
  230. package/src/lib/server/tool-retry.ts +4 -3
  231. package/src/lib/server/usage/usage-repository.ts +30 -0
  232. package/src/lib/server/wallet/wallet-portfolio.ts +29 -0
  233. package/src/lib/server/webhooks/webhook-repository.ts +10 -0
  234. package/src/lib/server/ws-hub.ts +5 -2
  235. package/src/lib/strip-internal-metadata.test.ts +78 -37
  236. package/src/lib/strip-internal-metadata.ts +20 -6
  237. package/src/stores/use-approval-store.ts +7 -1
  238. package/src/stores/use-chat-store.test.ts +54 -0
  239. package/src/stores/use-chat-store.ts +26 -6
  240. package/src/types/index.ts +6 -0
  241. /package/{bundled-skills → skills}/google-workspace/SKILL.md +0 -0
@@ -1,20 +1,25 @@
1
1
  import assert from 'node:assert/strict'
2
2
  import { describe, it } from 'node:test'
3
+ import type { Agent, ProviderType } from '@/types'
3
4
  import { isWorkerOnlyAgent, buildWorkerOnlyAgentMessage } from './agent-availability'
4
5
 
5
6
  describe('isWorkerOnlyAgent', () => {
6
- const CLI_PROVIDERS = ['claude-cli', 'codex-cli', 'opencode-cli', 'gemini-cli', 'openclaw']
7
- const NON_CLI_PROVIDERS = ['openai', 'anthropic', 'google', 'deepseek', 'groq', 'custom']
7
+ const CLI_PROVIDERS = ['claude-cli', 'codex-cli', 'opencode-cli', 'gemini-cli', 'openclaw'] satisfies ProviderType[]
8
+ const NON_CLI_PROVIDERS = ['openai', 'anthropic', 'google', 'deepseek', 'groq', 'together'] satisfies ProviderType[]
9
+
10
+ function withProvider(provider: unknown): Pick<Agent, 'provider'> {
11
+ return { provider } as Pick<Agent, 'provider'>
12
+ }
8
13
 
9
14
  for (const provider of CLI_PROVIDERS) {
10
15
  it(`returns true for ${provider}`, () => {
11
- assert.equal(isWorkerOnlyAgent({ provider }), true)
16
+ assert.equal(isWorkerOnlyAgent(withProvider(provider)), true)
12
17
  })
13
18
  }
14
19
 
15
20
  for (const provider of NON_CLI_PROVIDERS) {
16
21
  it(`returns false for ${provider}`, () => {
17
- assert.equal(isWorkerOnlyAgent({ provider }), false)
22
+ assert.equal(isWorkerOnlyAgent(withProvider(provider)), false)
18
23
  })
19
24
  }
20
25
 
@@ -27,7 +32,7 @@ describe('isWorkerOnlyAgent', () => {
27
32
  })
28
33
 
29
34
  it('returns false for empty provider string', () => {
30
- assert.equal(isWorkerOnlyAgent({ provider: '' }), false)
35
+ assert.equal(isWorkerOnlyAgent(withProvider('')), false)
31
36
  })
32
37
  })
33
38
 
@@ -3,22 +3,21 @@
3
3
  *
4
4
  * When an agent is trashed, related entities (tasks, schedules, watch jobs,
5
5
  * connectors, webhooks, delegation jobs, chatroom memberships) must be
6
- * suspended to prevent phantom daemon activity. On permanent delete the
6
+ * suspended to prevent phantom daemon activity. On permanent delete the
7
7
  * referencing rows are hard-removed.
8
8
  */
9
9
 
10
10
  import {
11
- loadTasks,
12
- upsertStoredItems,
13
- loadSchedules,
14
- loadWatchJobs,
15
- loadConnectors,
11
+ deleteDelegationJob,
16
12
  loadDelegationJobs,
17
- loadWebhooks,
18
- loadChatrooms,
19
- deleteStoredItem,
20
- } from '@/lib/server/storage'
21
- import type { StorageCollection } from '@/lib/server/storage'
13
+ saveDelegationJobRecords,
14
+ } from '@/lib/server/agents/delegation-job-repository'
15
+ import { loadChatrooms, saveChatrooms } from '@/lib/server/chatrooms/chatroom-repository'
16
+ import { loadConnectors, saveConnectors } from '@/lib/server/connectors/connector-repository'
17
+ import { deleteWatchJob, loadWatchJobs, upsertWatchJobs } from '@/lib/server/runtime/watch-job-repository'
18
+ import { deleteSchedule, loadSchedules, upsertSchedules } from '@/lib/server/schedules/schedule-repository'
19
+ import { deleteTask, loadTasks, saveTaskMany } from '@/lib/server/tasks/task-repository'
20
+ import { loadWebhooks, saveWebhooks } from '@/lib/server/webhooks/webhook-repository'
22
21
 
23
22
  interface CascadeCounts {
24
23
  tasks: number
@@ -39,7 +38,7 @@ export function suspendAgentReferences(agentId: string): CascadeCounts {
39
38
 
40
39
  // 1. Tasks — cancel active ones
41
40
  const tasks = loadTasks()
42
- const taskUpdates: Array<[string, unknown]> = []
41
+ const taskUpdates: Array<[string, Record<string, unknown>]> = []
43
42
  for (const t of Object.values(tasks) as unknown as Array<Record<string, unknown>>) {
44
43
  if (!t || t.agentId !== agentId) continue
45
44
  const status = t.status as string | undefined
@@ -50,13 +49,13 @@ export function suspendAgentReferences(agentId: string): CascadeCounts {
50
49
  }
51
50
  }
52
51
  if (taskUpdates.length) {
53
- upsertStoredItems('tasks', taskUpdates)
52
+ saveTaskMany(taskUpdates)
54
53
  counts.tasks = taskUpdates.length
55
54
  }
56
55
 
57
56
  // 2. Schedules — pause (with marker for restore)
58
57
  const schedules = loadSchedules()
59
- const schedUpdates: Array<[string, unknown]> = []
58
+ const schedUpdates: Array<[string, Record<string, unknown>]> = []
60
59
  for (const s of Object.values(schedules) as unknown as Array<Record<string, unknown>>) {
61
60
  if (!s || s.agentId !== agentId) continue
62
61
  if (s.enabled === false) continue
@@ -65,13 +64,13 @@ export function suspendAgentReferences(agentId: string): CascadeCounts {
65
64
  schedUpdates.push([s.id as string, s])
66
65
  }
67
66
  if (schedUpdates.length) {
68
- upsertStoredItems('schedules', schedUpdates)
67
+ upsertSchedules(schedUpdates)
69
68
  counts.schedules = schedUpdates.length
70
69
  }
71
70
 
72
71
  // 3. Watch jobs — cancel active
73
72
  const watchJobs = loadWatchJobs()
74
- const wjUpdates: Array<[string, unknown]> = []
73
+ const wjUpdates: Array<[string, Record<string, unknown>]> = []
75
74
  for (const w of Object.values(watchJobs) as unknown as Array<Record<string, unknown>>) {
76
75
  if (!w || w.agentId !== agentId) continue
77
76
  if (w.status === 'cancelled') continue
@@ -79,26 +78,28 @@ export function suspendAgentReferences(agentId: string): CascadeCounts {
79
78
  wjUpdates.push([w.id as string, w])
80
79
  }
81
80
  if (wjUpdates.length) {
82
- upsertStoredItems('watch_jobs', wjUpdates)
81
+ upsertWatchJobs(wjUpdates)
83
82
  counts.watchJobs = wjUpdates.length
84
83
  }
85
84
 
86
85
  // 4. Connectors — detach agent (keep connector alive but unrouted)
87
- const connectors = loadConnectors()
88
- const connUpdates: Array<[string, unknown]> = []
89
- for (const c of Object.values(connectors) as unknown as Array<Record<string, unknown>>) {
90
- if (!c || c.agentId !== agentId) continue
91
- c.agentId = null
92
- connUpdates.push([c.id as string, c])
93
- }
94
- if (connUpdates.length) {
95
- upsertStoredItems('connectors', connUpdates)
96
- counts.connectors = connUpdates.length
86
+ {
87
+ const connectors = loadConnectors()
88
+ let connectorUpdates = 0
89
+ for (const c of Object.values(connectors)) {
90
+ if (!c || c.agentId !== agentId) continue
91
+ c.agentId = null
92
+ connectorUpdates += 1
93
+ }
94
+ if (connectorUpdates > 0) {
95
+ saveConnectors(connectors)
96
+ counts.connectors = connectorUpdates
97
+ }
97
98
  }
98
99
 
99
100
  // 5. Delegation jobs — cancel queued/running
100
101
  const delegationJobs = loadDelegationJobs()
101
- const djUpdates: Array<[string, unknown]> = []
102
+ const djUpdates: Array<[string, Record<string, unknown>]> = []
102
103
  for (const d of Object.values(delegationJobs) as unknown as Array<Record<string, unknown>>) {
103
104
  if (!d || d.agentId !== agentId) continue
104
105
  const status = d.status as string | undefined
@@ -108,22 +109,22 @@ export function suspendAgentReferences(agentId: string): CascadeCounts {
108
109
  }
109
110
  }
110
111
  if (djUpdates.length) {
111
- upsertStoredItems('delegation_jobs', djUpdates)
112
+ saveDelegationJobRecords(djUpdates)
112
113
  counts.delegationJobs = djUpdates.length
113
114
  }
114
115
 
115
116
  // 6. Webhooks — disable
116
117
  const webhooks = loadWebhooks()
117
- const whUpdates: Array<[string, unknown]> = []
118
- for (const w of Object.values(webhooks) as unknown as Array<Record<string, unknown>>) {
118
+ let webhookUpdates = 0
119
+ for (const w of Object.values(webhooks)) {
119
120
  if (!w || w.agentId !== agentId) continue
120
121
  if (w.enabled === false) continue
121
122
  w.enabled = false
122
- whUpdates.push([w.id as string, w])
123
+ webhookUpdates += 1
123
124
  }
124
- if (whUpdates.length) {
125
- upsertStoredItems('webhooks', whUpdates)
126
- counts.webhooks = whUpdates.length
125
+ if (webhookUpdates > 0) {
126
+ saveWebhooks(webhooks)
127
+ counts.webhooks = webhookUpdates
127
128
  }
128
129
 
129
130
  // 7. Chatrooms — remove agent from member arrays
@@ -138,23 +139,25 @@ export function suspendAgentReferences(agentId: string): CascadeCounts {
138
139
  export function purgeAgentReferences(agentId: string): CascadeCounts {
139
140
  const counts: CascadeCounts = { tasks: 0, schedules: 0, watchJobs: 0, connectors: 0, delegationJobs: 0, webhooks: 0, chatrooms: 0 }
140
141
 
141
- counts.tasks = deleteMatching('tasks', loadTasks(), agentId)
142
- counts.schedules = deleteMatching('schedules', loadSchedules(), agentId)
143
- counts.watchJobs = deleteMatching('watch_jobs', loadWatchJobs(), agentId)
144
- counts.delegationJobs = deleteMatching('delegation_jobs', loadDelegationJobs(), agentId)
145
- counts.webhooks = deleteMatching('webhooks', loadWebhooks(), agentId)
142
+ counts.tasks = deleteMatching(loadTasks(), agentId, deleteTask)
143
+ counts.schedules = deleteMatching(loadSchedules(), agentId, deleteSchedule)
144
+ counts.watchJobs = deleteMatching(loadWatchJobs(), agentId, deleteWatchJob)
145
+ counts.delegationJobs = deleteMatching(loadDelegationJobs(), agentId, deleteDelegationJob)
146
+ counts.webhooks = purgeWebhooks(agentId)
146
147
 
147
148
  // Connectors: detach agent but keep the connector record
148
- const connectors = loadConnectors()
149
- const connUpdates: Array<[string, unknown]> = []
150
- for (const c of Object.values(connectors) as unknown as Array<Record<string, unknown>>) {
151
- if (!c || c.agentId !== agentId) continue
152
- c.agentId = null
153
- connUpdates.push([c.id as string, c])
154
- }
155
- if (connUpdates.length) {
156
- upsertStoredItems('connectors', connUpdates)
157
- counts.connectors = connUpdates.length
149
+ {
150
+ const connectors = loadConnectors()
151
+ let connectorUpdates = 0
152
+ for (const c of Object.values(connectors)) {
153
+ if (!c || c.agentId !== agentId) continue
154
+ c.agentId = null
155
+ connectorUpdates += 1
156
+ }
157
+ if (connectorUpdates > 0) {
158
+ saveConnectors(connectors)
159
+ counts.connectors = connectorUpdates
160
+ }
158
161
  }
159
162
 
160
163
  counts.chatrooms = removeAgentFromChatrooms(agentId)
@@ -167,7 +170,7 @@ export function purgeAgentReferences(agentId: string): CascadeCounts {
167
170
  /** Re-enable schedules that were paused by trash. */
168
171
  export function restoreAgentSchedules(agentId: string): number {
169
172
  const schedules = loadSchedules()
170
- const updates: Array<[string, unknown]> = []
173
+ const updates: Array<[string, Record<string, unknown>]> = []
171
174
  for (const s of Object.values(schedules) as unknown as Array<Record<string, unknown>>) {
172
175
  if (!s || s.agentId !== agentId) continue
173
176
  if (!s.suspendedByTrash) continue
@@ -176,7 +179,7 @@ export function restoreAgentSchedules(agentId: string): number {
176
179
  updates.push([s.id as string, s])
177
180
  }
178
181
  if (updates.length) {
179
- upsertStoredItems('schedules', updates)
182
+ upsertSchedules(updates)
180
183
  }
181
184
  return updates.length
182
185
  }
@@ -184,15 +187,15 @@ export function restoreAgentSchedules(agentId: string): number {
184
187
  // ── Internals ───────────────────────────────────────────────────────────
185
188
 
186
189
  function deleteMatching<T extends { agentId?: string | null; id?: string | null }>(
187
- table: StorageCollection,
188
190
  collection: Record<string, T>,
189
191
  agentId: string,
192
+ deleteItem: (id: string) => void,
190
193
  ): number {
191
194
  let count = 0
192
195
  for (const item of Object.values(collection)) {
193
196
  if (!item || item.agentId !== agentId) continue
194
197
  if (!item.id) continue
195
- deleteStoredItem(table, item.id)
198
+ deleteItem(item.id)
196
199
  count++
197
200
  }
198
201
  return count
@@ -200,7 +203,7 @@ function deleteMatching<T extends { agentId?: string | null; id?: string | null
200
203
 
201
204
  function removeAgentFromChatrooms(agentId: string): number {
202
205
  const chatrooms = loadChatrooms()
203
- const updates: Array<[string, unknown]> = []
206
+ let changedCount = 0
204
207
  for (const room of Object.values(chatrooms) as unknown as Array<Record<string, unknown>>) {
205
208
  if (!room) continue
206
209
  let changed = false
@@ -220,10 +223,27 @@ function removeAgentFromChatrooms(agentId: string): number {
220
223
  room.agentIds = agentIds.filter((id) => id !== agentId)
221
224
  if ((room.agentIds as string[]).length !== before) changed = true
222
225
  }
223
- if (changed) updates.push([room.id as string, room])
226
+ if (changed) changedCount += 1
224
227
  }
225
- if (updates.length) {
226
- upsertStoredItems('chatrooms', updates)
228
+ if (changedCount > 0) {
229
+ saveChatrooms(chatrooms)
227
230
  }
228
- return updates.length
231
+ return changedCount
232
+ }
233
+
234
+ function purgeWebhooks(agentId: string): number {
235
+ const webhooks = loadWebhooks() as Record<string, Record<string, unknown>>
236
+ const remaining: Record<string, Record<string, unknown>> = {}
237
+ let count = 0
238
+ for (const [id, webhook] of Object.entries(webhooks)) {
239
+ if (webhook && webhook.agentId === agentId) {
240
+ count += 1
241
+ continue
242
+ }
243
+ remaining[id] = webhook
244
+ }
245
+ if (count > 0) {
246
+ saveWebhooks(remaining)
247
+ }
248
+ return count
229
249
  }
@@ -1,5 +1,7 @@
1
- import { loadAgents, loadTasks, loadSessions } from '@/lib/server/storage'
2
1
  import type { Agent, BoardTask } from '@/types'
2
+ import { loadAgents } from '@/lib/server/agents/agent-repository'
3
+ import { loadSessions } from '@/lib/server/sessions/session-repository'
4
+ import { loadTasks } from '@/lib/server/tasks/task-repository'
3
5
 
4
6
  export interface AgentDirectoryEntry {
5
7
  id: string
@@ -52,19 +54,36 @@ export function getAgentDirectory(excludeId?: string): AgentDirectoryEntry[] {
52
54
  return entries
53
55
  }
54
56
 
55
- export function buildAgentAwarenessBlock(excludeId: string): string {
56
- const directory = getAgentDirectory(excludeId)
57
+ export function buildAgentAwarenessBlock(
58
+ excludeId: string,
59
+ opts?: {
60
+ delegationTargetMode?: 'all' | 'selected'
61
+ delegationTargetAgentIds?: string[]
62
+ },
63
+ ): string {
64
+ let directory = getAgentDirectory(excludeId)
57
65
  if (!directory.length) return ''
58
66
 
67
+ const isFiltered = opts?.delegationTargetMode === 'selected'
68
+ if (isFiltered) {
69
+ const allowedIds = new Set(opts.delegationTargetAgentIds || [])
70
+ directory = directory.filter((entry) => allowedIds.has(entry.id))
71
+ if (!directory.length) return ''
72
+ }
73
+
59
74
  const lines = directory.map((entry) => {
60
75
  const caps = entry.capabilities.length ? ` (${entry.capabilities.join(', ')})` : ''
61
76
  const status = entry.statusDetail || entry.status
62
77
  return `- **${entry.name}** [id: ${entry.id}]${caps} — ${status}`
63
78
  })
64
79
 
80
+ const header = isFiltered
81
+ ? 'These are the ONLY agents I can delegate tasks to. Do not attempt to delegate to any other agents:'
82
+ : 'These are the other agents I work alongside. I can hand off tasks to any of them if their skills are a better fit:'
83
+
65
84
  return [
66
85
  '## My Colleagues',
67
- 'These are the other agents I work alongside. I can hand off tasks to any of them if their skills are a better fit:',
86
+ header,
68
87
  ...lines,
69
88
  ].join('\n')
70
89
  }
@@ -0,0 +1,90 @@
1
+ import type { Agent } from '@/types'
2
+
3
+ import {
4
+ deleteAgent as deleteStoredAgent,
5
+ loadAgent as loadStoredAgent,
6
+ loadAgents as loadStoredAgents,
7
+ loadTrashedAgents as loadStoredTrashedAgents,
8
+ patchAgent as patchStoredAgent,
9
+ saveAgents as saveStoredAgents,
10
+ upsertAgent as upsertStoredAgent,
11
+ } from '@/lib/server/storage'
12
+ import { createRecordRepository } from '@/lib/server/persistence/repository-utils'
13
+
14
+ type AgentRepositoryOptions = { includeTrashed?: boolean }
15
+
16
+ export const agentRepository = createRecordRepository<Agent, AgentRepositoryOptions>(
17
+ 'agents',
18
+ {
19
+ get(id, options) {
20
+ return loadStoredAgent(id, options) as Agent | null
21
+ },
22
+ list(options) {
23
+ return loadStoredAgents(options) as Record<string, Agent>
24
+ },
25
+ upsert(id, value) {
26
+ upsertStoredAgent(id, value)
27
+ },
28
+ replace(data) {
29
+ saveStoredAgents(data)
30
+ },
31
+ patch(id, updater) {
32
+ return patchStoredAgent(id, updater as (current: Agent | null) => Agent | null) as Agent | null
33
+ },
34
+ delete(id) {
35
+ deleteStoredAgent(id)
36
+ },
37
+ },
38
+ )
39
+
40
+ export function getAgent(id: string, options?: AgentRepositoryOptions): Agent | null {
41
+ return agentRepository.get(id, options)
42
+ }
43
+
44
+ export function getAgents(ids: string[], options?: AgentRepositoryOptions): Record<string, Agent> {
45
+ return agentRepository.getMany(ids, options)
46
+ }
47
+
48
+ export function listAgents(options?: AgentRepositoryOptions): Record<string, Agent> {
49
+ return agentRepository.list(options)
50
+ }
51
+
52
+ export function saveAgent(id: string, agent: Agent | Record<string, unknown>): void {
53
+ agentRepository.upsert(id, agent as Agent)
54
+ }
55
+
56
+ export function saveAgentMany(entries: Array<[string, Agent | Record<string, unknown>]>): void {
57
+ agentRepository.upsertMany(entries as Array<[string, Agent]>)
58
+ }
59
+
60
+ export function replaceAgents(agents: Record<string, Agent | Record<string, unknown>>): void {
61
+ agentRepository.replace(agents as Record<string, Agent>)
62
+ }
63
+
64
+ export function patchAgent(id: string, updater: (current: Agent | null) => Agent | null): Agent | null {
65
+ return agentRepository.patch(id, updater)
66
+ }
67
+
68
+ export function deleteAgent(id: string): void {
69
+ agentRepository.delete(id)
70
+ }
71
+
72
+ export function loadAgents(options?: AgentRepositoryOptions): Record<string, Agent> {
73
+ return listAgents(options)
74
+ }
75
+
76
+ export function loadAgent(id: string, options?: AgentRepositoryOptions): Agent | null {
77
+ return getAgent(id, options)
78
+ }
79
+
80
+ export function saveAgents(agents: Record<string, Agent | Record<string, unknown>>): void {
81
+ replaceAgents(agents)
82
+ }
83
+
84
+ export function upsertAgent(id: string, agent: Agent | Record<string, unknown>): void {
85
+ saveAgent(id, agent)
86
+ }
87
+
88
+ export function loadTrashedAgents(): Record<string, Agent> {
89
+ return loadStoredTrashedAgents() as Record<string, Agent>
90
+ }
@@ -0,0 +1,53 @@
1
+ import type { DelegationJobRecord } from '@/types'
2
+
3
+ import {
4
+ deleteDelegationJob as deleteStoredDelegationJob,
5
+ loadDelegationJobItem as loadStoredDelegationJob,
6
+ loadDelegationJobs as loadStoredDelegationJobs,
7
+ patchDelegationJob as patchStoredDelegationJob,
8
+ upsertDelegationJob as upsertStoredDelegationJob,
9
+ } from '@/lib/server/storage'
10
+ import { createRecordRepository } from '@/lib/server/persistence/repository-utils'
11
+
12
+ export const delegationJobRepository = createRecordRepository<DelegationJobRecord>(
13
+ 'delegation-jobs',
14
+ {
15
+ get(id) {
16
+ return loadStoredDelegationJob(id) as DelegationJobRecord | null
17
+ },
18
+ list() {
19
+ return loadStoredDelegationJobs() as Record<string, DelegationJobRecord>
20
+ },
21
+ upsert(id, value) {
22
+ upsertStoredDelegationJob(id, value as DelegationJobRecord)
23
+ },
24
+ patch(id, updater) {
25
+ return patchStoredDelegationJob(
26
+ id,
27
+ updater as (current: DelegationJobRecord | null) => DelegationJobRecord | null,
28
+ ) as DelegationJobRecord | null
29
+ },
30
+ delete(id) {
31
+ deleteStoredDelegationJob(id)
32
+ },
33
+ },
34
+ )
35
+
36
+ export const getDelegationJobRecord = (id: string) => delegationJobRepository.get(id)
37
+ export const getDelegationJobRecords = (ids: string[]) => delegationJobRepository.getMany(ids)
38
+ export const listDelegationJobRecords = () => delegationJobRepository.list()
39
+ export const saveDelegationJobRecord = (id: string, value: DelegationJobRecord | Record<string, unknown>) =>
40
+ delegationJobRepository.upsert(id, value as DelegationJobRecord)
41
+ export const saveDelegationJobRecords = (entries: Array<[string, DelegationJobRecord | Record<string, unknown>]>) =>
42
+ delegationJobRepository.upsertMany(entries as Array<[string, DelegationJobRecord]>)
43
+ export const patchDelegationJobRecord = (
44
+ id: string,
45
+ updater: (current: DelegationJobRecord | null) => DelegationJobRecord | null,
46
+ ) => delegationJobRepository.patch(id, updater)
47
+ export const deleteDelegationJobRecord = (id: string) => delegationJobRepository.delete(id)
48
+
49
+ export const loadDelegationJobs = listDelegationJobRecords
50
+ export const loadDelegationJob = getDelegationJobRecord
51
+ export const upsertDelegationJob = saveDelegationJobRecord
52
+ export const patchDelegationJob = patchDelegationJobRecord
53
+ export const deleteDelegationJob = deleteDelegationJobRecord
@@ -1,13 +1,20 @@
1
1
  import { genId } from '@/lib/id'
2
2
  import { hmrSingleton } from '@/lib/shared-utils'
3
3
  import type { DelegationJobArtifact, DelegationJobCheckpoint, DelegationJobRecord, DelegationJobStatus } from '@/types'
4
- import { loadDelegationJobs, loadDelegationJobItem, loadSession, logActivity, patchDelegationJob, upsertDelegationJob } from '@/lib/server/storage'
4
+ import {
5
+ loadDelegationJob,
6
+ loadDelegationJobs,
7
+ patchDelegationJob,
8
+ upsertDelegationJob,
9
+ } from '@/lib/server/agents/delegation-job-repository'
10
+ import { logActivity } from '@/lib/server/activity/activity-log'
5
11
  import { logExecution } from '@/lib/server/execution-log'
6
12
  import { log } from '@/lib/server/logger'
7
13
  import { debug } from '@/lib/server/debug'
8
14
  import { createNotification } from '@/lib/server/create-notification'
9
15
  import { enqueueSystemEvent } from '@/lib/server/runtime/system-events'
10
16
  import { ensureDelegationMission, syncDelegationMissionFromJob } from '@/lib/server/missions/mission-service'
17
+ import { loadSession } from '@/lib/server/sessions/session-repository'
11
18
  import { notify } from '@/lib/server/ws-hub'
12
19
 
13
20
  interface DelegationRuntimeHandle {
@@ -115,7 +122,7 @@ export function createDelegationJob(input: CreateDelegationJobInput): Delegation
115
122
  }
116
123
 
117
124
  export function getDelegationJob(id: string): DelegationJobRecord | null {
118
- const current = loadDelegationJobItem(id)
125
+ const current = loadDelegationJob(id)
119
126
  if (!current || typeof current !== 'object') return null
120
127
  return current as DelegationJobRecord
121
128
  }
@@ -144,9 +151,9 @@ export function updateDelegationJob(
144
151
  }
145
152
  })
146
153
  if (!result) return null
147
- const synced = syncDelegationMissionFromJob(id) || result
154
+ syncDelegationMissionFromJob(id)
148
155
  notifyDelegationJobsChanged()
149
- return getDelegationJob(id) || synced
156
+ return getDelegationJob(id) || result
150
157
  }
151
158
 
152
159
  export function appendDelegationCheckpoint(
@@ -0,0 +1,35 @@
1
+ import type { GuardianCheckpoint } from '@/types'
2
+
3
+ import {
4
+ loadGuardianCheckpoints as loadStoredGuardianCheckpoints,
5
+ patchGuardianCheckpoint as patchStoredGuardianCheckpoint,
6
+ upsertGuardianCheckpoint as upsertStoredGuardianCheckpoint,
7
+ } from '@/lib/server/storage'
8
+ import { createRecordRepository } from '@/lib/server/persistence/repository-utils'
9
+
10
+ export const guardianCheckpointRepository = createRecordRepository<GuardianCheckpoint>(
11
+ 'guardian-checkpoints',
12
+ {
13
+ get(id) {
14
+ return loadStoredGuardianCheckpoints()[id] || null
15
+ },
16
+ list() {
17
+ return loadStoredGuardianCheckpoints()
18
+ },
19
+ upsert(id, value) {
20
+ upsertStoredGuardianCheckpoint(id, value as GuardianCheckpoint)
21
+ },
22
+ patch(id, updater) {
23
+ return patchStoredGuardianCheckpoint(id, updater)
24
+ },
25
+ },
26
+ )
27
+
28
+ export const loadGuardianCheckpoints = () => guardianCheckpointRepository.list()
29
+ export const loadGuardianCheckpoint = (id: string) => guardianCheckpointRepository.get(id)
30
+ export const upsertGuardianCheckpoint = (id: string, value: GuardianCheckpoint | Record<string, unknown>) =>
31
+ guardianCheckpointRepository.upsert(id, value as GuardianCheckpoint)
32
+ export const patchGuardianCheckpoint = (
33
+ id: string,
34
+ updater: (current: GuardianCheckpoint | null) => GuardianCheckpoint | null,
35
+ ) => guardianCheckpointRepository.patch(id, updater)
@@ -1,12 +1,12 @@
1
1
  import { execSync } from 'node:child_process'
2
2
  import path from 'node:path'
3
3
  import type { ApprovalRequest, GuardianCheckpoint } from '@/types'
4
+ import { loadApprovals } from '@/lib/server/approvals/approval-repository'
4
5
  import {
5
- loadApprovals,
6
6
  loadGuardianCheckpoints,
7
7
  patchGuardianCheckpoint,
8
8
  upsertGuardianCheckpoint,
9
- } from '@/lib/server/storage'
9
+ } from '@/lib/server/agents/guardian-checkpoint-repository'
10
10
  import { requestApproval } from '@/lib/server/approvals'
11
11
  import { errorMessage } from '@/lib/shared-utils'
12
12
  import { genId } from '@/lib/id'
@@ -1,10 +1,17 @@
1
1
  import { hmrSingleton } from '@/lib/shared-utils'
2
2
  import type { GoalContract, Message, MessageToolEvent, Session } from '@/types'
3
3
  import { mergeGoalContracts, parseGoalContractFromText, parseMainLoopPlan, parseMainLoopReview } from '@/lib/server/agents/autonomy-contract'
4
+ import {
5
+ deletePersistedMainLoopState,
6
+ loadPersistedMainLoopState,
7
+ upsertPersistedMainLoopState,
8
+ } from '@/lib/server/agents/main-loop-state-repository'
9
+ import { loadAgents } from '@/lib/server/agents/agent-repository'
4
10
  import { assessAutonomyRun } from '@/lib/server/autonomy/supervisor-reflection'
5
11
  import { enqueueSystemEvent } from '@/lib/server/runtime/system-events'
6
- import { loadAgents, loadSessions, loadSettings, loadPersistedMainLoopState, upsertPersistedMainLoopState, deletePersistedMainLoopState } from '@/lib/server/storage'
7
12
  import { buildMissionHeartbeatPrompt as buildMissionHeartbeatPromptFromMission, getMissionForSession } from '@/lib/server/missions/mission-service'
13
+ import { loadSettings } from '@/lib/server/settings/settings-repository'
14
+ import { getSession, loadSessions } from '@/lib/server/sessions/session-repository'
8
15
 
9
16
  const LEGACY_META_LINE_RE = /\[(?:MAIN_LOOP_META|MAIN_LOOP_PLAN|MAIN_LOOP_REVIEW|AGENT_HEARTBEAT_META)\]\s*(\{[^\n]*\})?/i
10
17
  const HEARTBEAT_META_RE = /\[AGENT_HEARTBEAT_META\]\s*(\{[^\n]*\})/i
@@ -12,7 +19,7 @@ const MAX_PENDING_EVENTS = 16
12
19
  const MAX_TIMELINE_ITEMS = 40
13
20
  const MAX_WORKING_MEMORY_NOTES = 12
14
21
  const DEFAULT_FOLLOWUP_DELAY_MS = 1500
15
- const DEFAULT_MAX_FOLLOWUP_CHAIN = 6
22
+ const DEFAULT_MAX_FOLLOWUP_CHAIN = 4
16
23
  const MAX_LIFETIME_ITERATIONS = 200
17
24
 
18
25
  export interface MainLoopState {
@@ -437,7 +444,7 @@ function hydrateStateFromSession(sessionId: string): MainLoopState | null {
437
444
  }
438
445
 
439
446
  function persistState(sessionId: string, state: MainLoopState): void {
440
- upsertPersistedMainLoopState(sessionId, state as unknown)
447
+ upsertPersistedMainLoopState(sessionId, state as unknown as Record<string, unknown>)
441
448
  }
442
449
 
443
450
  function getOrCreateState(sessionId: string): MainLoopState | null {
@@ -738,7 +745,7 @@ export function isMainSession(session: unknown): boolean {
738
745
  export function buildMainLoopHeartbeatPrompt(session: unknown, fallbackPrompt: string): string {
739
746
  const candidate = asSession(session)
740
747
  if (!candidate?.id) return fallbackPrompt
741
- const persistedSession = loadSessions()[String(candidate.id)] as Session | undefined
748
+ const persistedSession = getSession(String(candidate.id)) as Session | undefined
742
749
  const missionPrompt = buildMissionHeartbeatPromptFromMission(
743
750
  persistedSession || candidate as Session,
744
751
  fallbackPrompt,
@@ -778,6 +785,7 @@ export function buildMainLoopHeartbeatPrompt(session: unknown, fallbackPrompt: s
778
785
  boundedFallbackPrompt ? `Base heartbeat instructions:\n${boundedFallbackPrompt}` : '',
779
786
  '',
780
787
  'You are checking the durable main mission thread for this agent.',
788
+ 'Keep this status check brief — 5-10 tool calls maximum. Read key state, summarize progress, and report. Do not attempt fixes or deep investigation during heartbeats.',
781
789
  'Use only the current goal, plan, next action, and pending external events shown above.',
782
790
  'Do not infer or repeat old tasks from prior heartbeats.',
783
791
  'Prefer taking the single highest-value next step over restating the plan. Do not repeat completed work.',
@@ -1072,8 +1080,8 @@ export function handleMainLoopRunResult(input: HandleMainLoopRunResultInput): Ma
1072
1080
  || (needsReplan
1073
1081
  ? 'Replan from the latest outcome, then execute only the highest-value remaining step. Do not repeat completed work.'
1074
1082
  : state.nextAction
1075
- ? `Continue the objective. Resume from this next action: ${state.nextAction}`
1076
- : 'Continue the objective and finish the next highest-value remaining step.')
1083
+ ? `Continue. Next action: ${state.nextAction}. Do not repeat tool calls from previous turns.`
1084
+ : `Continue. You have used ${state.followupChainCount} of ${limit} followup turns. Focus on completing one concrete step, then summarize progress.`)
1077
1085
  followup = {
1078
1086
  message,
1079
1087
  delayMs: DEFAULT_FOLLOWUP_DELAY_MS,