@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
@@ -0,0 +1,296 @@
1
+ import { CONTEXT_OVERFLOW_RE } from '@/lib/providers/error-classification'
2
+ import type { ProviderType } from '@/types'
3
+ import { getEnabledCapabilityIds } from '@/lib/capability-selection'
4
+ import { isLocalOpenClawEndpoint } from '@/lib/openclaw/openclaw-endpoint'
5
+ import { streamAgentChat } from '@/lib/server/chat-execution/stream-agent-chat'
6
+ import { applyContextClearBoundary } from '@/lib/server/chat-execution/chat-execution-utils'
7
+ import { estimateCost } from '@/lib/server/cost'
8
+ import { log } from '@/lib/server/logger'
9
+ import { runCapabilityHook } from '@/lib/server/native-capabilities'
10
+ import { markProviderFailure } from '@/lib/server/provider-health'
11
+ import {
12
+ getCachedLlmResponse,
13
+ resolveLlmResponseCacheConfig,
14
+ setCachedLlmResponse,
15
+ type LlmResponseCacheKeyInput,
16
+ } from '@/lib/server/llm-response-cache'
17
+ import {
18
+ activeSessionProcesses,
19
+ clearActiveSessionProcess,
20
+ registerActiveSessionProcess,
21
+ } from '@/lib/server/runtime/runtime-state'
22
+ import { perf } from '@/lib/server/runtime/perf'
23
+ import { getSessionMessages } from '@/lib/server/sessions/session-repository'
24
+ import { notify } from '@/lib/server/ws-hub'
25
+ import { errorMessage as toErrorMessage } from '@/lib/shared-utils'
26
+
27
+ import type { ExecuteChatTurnInput } from './chat-execution-types'
28
+ import type { PartialAssistantPersistence } from '@/lib/server/chat-execution/chat-turn-partial-persistence'
29
+ import type { PreparedExecutableChatTurn } from '@/lib/server/chat-execution/chat-turn-preparation'
30
+ import type { ToolRoutingResult } from '@/lib/server/chat-execution/chat-turn-tool-routing'
31
+
32
+ const TAG = 'chat-execution'
33
+
34
+ export interface ExecutedPreparedChatTurn {
35
+ fullResponse: string
36
+ errorMessage?: string
37
+ toolRoutingResult: ToolRoutingResult | null
38
+ responseCacheHit: boolean
39
+ durationMs: number
40
+ directUsage: {
41
+ inputTokens: number
42
+ outputTokens: number
43
+ received: boolean
44
+ }
45
+ }
46
+
47
+ export async function executePreparedChatTurn(params: {
48
+ input: ExecuteChatTurnInput
49
+ prepared: PreparedExecutableChatTurn
50
+ partialPersistence: PartialAssistantPersistence
51
+ preflightToolRoutingResult?: ToolRoutingResult | null
52
+ }): Promise<ExecutedPreparedChatTurn> {
53
+ const { input, prepared, partialPersistence, preflightToolRoutingResult = null } = params
54
+ const {
55
+ sessionId,
56
+ imageUrl,
57
+ attachedFiles,
58
+ internal = false,
59
+ runId,
60
+ source = 'chat',
61
+ signal,
62
+ } = input
63
+ const {
64
+ sessionForRun,
65
+ appSettings,
66
+ lifecycleRunId,
67
+ extensionsForRun,
68
+ effectiveMessage,
69
+ providerType,
70
+ provider,
71
+ apiKey,
72
+ hasExtensions,
73
+ systemPrompt,
74
+ resolvedImagePath,
75
+ heartbeatLightContext,
76
+ isAutoRunNoHistory,
77
+ missionContextBlock,
78
+ } = prepared
79
+
80
+ const emit = partialPersistence.emit
81
+ const parseAndEmit = partialPersistence.parseAndEmit
82
+ let fullResponse = ''
83
+ let errorMessage: string | undefined
84
+
85
+ const directUsage = { inputTokens: 0, outputTokens: 0, received: false }
86
+ const responseCacheConfig = resolveLlmResponseCacheConfig(appSettings)
87
+ let responseCacheHit = false
88
+ let responseCacheInput: LlmResponseCacheKeyInput | null = null
89
+ let durationMs = 0
90
+ const startTs = Date.now()
91
+ const endLlmPerf = perf.start('chat-execution', 'llm-round-trip', {
92
+ sessionId,
93
+ provider: providerType,
94
+ hasExtensions,
95
+ extensionCount: getEnabledCapabilityIds(sessionForRun).length,
96
+ })
97
+
98
+ if (preflightToolRoutingResult) {
99
+ fullResponse = preflightToolRoutingResult.fullResponse
100
+ errorMessage = preflightToolRoutingResult.errorMessage
101
+ if (fullResponse) emit({ t: 'd', text: fullResponse })
102
+ partialPersistence.stop()
103
+ endLlmPerf({ durationMs: 0, cacheHit: false })
104
+ return {
105
+ fullResponse,
106
+ errorMessage,
107
+ toolRoutingResult: preflightToolRoutingResult,
108
+ responseCacheHit,
109
+ durationMs,
110
+ directUsage,
111
+ }
112
+ }
113
+
114
+ const abortController = new AbortController()
115
+ const abortFromOutside = () => abortController.abort()
116
+ if (signal) {
117
+ if (signal.aborted) abortController.abort()
118
+ else signal.addEventListener('abort', abortFromOutside)
119
+ }
120
+
121
+ registerActiveSessionProcess(sessionId, {
122
+ runId: runId || null,
123
+ source,
124
+ kill: () => abortController.abort(),
125
+ })
126
+
127
+ try {
128
+ const heartbeatHistory = isAutoRunNoHistory
129
+ ? (heartbeatLightContext ? [] : getSessionMessages(sessionId).slice(-6))
130
+ : undefined
131
+
132
+ const useLocalOpenClawNativeRuntime = providerType === 'openclaw' && isLocalOpenClawEndpoint(sessionForRun.apiEndpoint)
133
+ log.info(
134
+ TAG,
135
+ `provider=${providerType}, hasExtensions=${hasExtensions}, localOpenClawNative=${useLocalOpenClawNativeRuntime}, imagePath=${resolvedImagePath || 'none'}, attachedFiles=${attachedFiles?.length || 0}, extensions=${getEnabledCapabilityIds(sessionForRun).length}`,
136
+ )
137
+
138
+ if (hasExtensions) {
139
+ const result = await streamAgentChat({
140
+ session: sessionForRun,
141
+ message: effectiveMessage,
142
+ imagePath: resolvedImagePath,
143
+ imageUrl,
144
+ attachedFiles,
145
+ apiKey,
146
+ systemPrompt,
147
+ extraSystemContext: missionContextBlock ? [missionContextBlock] : undefined,
148
+ write: (raw) => parseAndEmit(raw),
149
+ history: heartbeatHistory ?? applyContextClearBoundary(getSessionMessages(sessionId)),
150
+ signal: abortController.signal,
151
+ source,
152
+ })
153
+ fullResponse = result.finalResponse || result.fullText
154
+ } else {
155
+ let directHistorySnapshot = isAutoRunNoHistory
156
+ ? (heartbeatLightContext ? [] : getSessionMessages(sessionId).slice(-6))
157
+ : applyContextClearBoundary(getSessionMessages(sessionId))
158
+ responseCacheInput = {
159
+ provider: providerType,
160
+ model: sessionForRun.model,
161
+ apiEndpoint: sessionForRun.apiEndpoint || '',
162
+ systemPrompt,
163
+ message: effectiveMessage,
164
+ imagePath: input.imagePath,
165
+ imageUrl,
166
+ attachedFiles,
167
+ history: directHistorySnapshot,
168
+ }
169
+ const canUseResponseCache = !internal && responseCacheConfig.enabled
170
+ const cached = canUseResponseCache
171
+ ? getCachedLlmResponse(responseCacheInput, responseCacheConfig)
172
+ : null
173
+ if (cached) {
174
+ responseCacheHit = true
175
+ fullResponse = cached.text
176
+ emit({
177
+ t: 'md',
178
+ text: JSON.stringify({
179
+ cache: {
180
+ hit: true,
181
+ ageMs: cached.ageMs,
182
+ provider: cached.provider,
183
+ model: cached.model,
184
+ },
185
+ }),
186
+ })
187
+ emit({ t: 'd', text: cached.text })
188
+ } else {
189
+ await runCapabilityHook(
190
+ 'llmInput',
191
+ {
192
+ session: sessionForRun,
193
+ runId: lifecycleRunId,
194
+ provider: providerType as ProviderType,
195
+ model: sessionForRun.model,
196
+ systemPrompt,
197
+ prompt: effectiveMessage,
198
+ historyMessages: directHistorySnapshot,
199
+ imagesCount: resolvedImagePath ? 1 : 0,
200
+ },
201
+ { enabledIds: extensionsForRun },
202
+ )
203
+ const doStreamChat = () => provider.handler.streamChat({
204
+ session: sessionForRun,
205
+ message: effectiveMessage,
206
+ imagePath: resolvedImagePath,
207
+ apiKey,
208
+ systemPrompt,
209
+ write: (raw: string) => parseAndEmit(raw),
210
+ active: activeSessionProcesses,
211
+ loadHistory: (sid: string) => {
212
+ if (sid === sessionId) return directHistorySnapshot
213
+ return isAutoRunNoHistory
214
+ ? getSessionMessages(sid).slice(-6)
215
+ : applyContextClearBoundary(getSessionMessages(sid))
216
+ },
217
+ onUsage: (usage) => {
218
+ directUsage.inputTokens = usage.inputTokens
219
+ directUsage.outputTokens = usage.outputTokens
220
+ directUsage.received = true
221
+ },
222
+ signal: abortController.signal,
223
+ })
224
+ try {
225
+ fullResponse = await doStreamChat()
226
+ } catch (streamErr: unknown) {
227
+ const streamErrMsg = toErrorMessage(streamErr)
228
+ const streamStatus = (streamErr as Record<string, unknown>)?.status
229
+ if (typeof streamStatus === 'number' && streamStatus === 400 && CONTEXT_OVERFLOW_RE.test(streamErrMsg)) {
230
+ log.warn('chat-run', 'Context overflow in direct path, reducing history and retrying', {
231
+ sessionId,
232
+ error: streamErrMsg,
233
+ historyLen: directHistorySnapshot.length,
234
+ })
235
+ directHistorySnapshot = directHistorySnapshot.slice(-10)
236
+ fullResponse = await doStreamChat()
237
+ } else {
238
+ throw streamErr
239
+ }
240
+ }
241
+ await runCapabilityHook(
242
+ 'llmOutput',
243
+ {
244
+ session: sessionForRun,
245
+ runId: lifecycleRunId,
246
+ provider: providerType as ProviderType,
247
+ model: sessionForRun.model,
248
+ assistantTexts: fullResponse ? [fullResponse] : [],
249
+ response: fullResponse,
250
+ usage: directUsage.received
251
+ ? {
252
+ input: directUsage.inputTokens,
253
+ output: directUsage.outputTokens,
254
+ total: directUsage.inputTokens + directUsage.outputTokens,
255
+ estimatedCost: estimateCost(sessionForRun.model, directUsage.inputTokens, directUsage.outputTokens),
256
+ }
257
+ : undefined,
258
+ },
259
+ { enabledIds: extensionsForRun },
260
+ )
261
+ if (canUseResponseCache && responseCacheInput && fullResponse) {
262
+ setCachedLlmResponse(responseCacheInput, fullResponse, responseCacheConfig)
263
+ }
264
+ }
265
+ }
266
+ durationMs = Date.now() - startTs
267
+ endLlmPerf({ durationMs, cacheHit: responseCacheHit })
268
+ } catch (err: unknown) {
269
+ endLlmPerf({ error: true })
270
+ errorMessage = toErrorMessage(err)
271
+ const failureText = errorMessage || 'Run failed.'
272
+ markProviderFailure(providerType, failureText, sessionForRun.credentialId)
273
+ emit({ t: 'err', text: failureText })
274
+ log.error('chat-run', `Run failed for session ${sessionId}`, {
275
+ runId,
276
+ source,
277
+ internal,
278
+ error: failureText,
279
+ stack: err instanceof Error ? err.stack?.split('\n').slice(0, 6).join('\n') : undefined,
280
+ })
281
+ } finally {
282
+ partialPersistence.stop()
283
+ clearActiveSessionProcess(sessionId)
284
+ notify('sessions')
285
+ if (signal) signal.removeEventListener('abort', abortFromOutside)
286
+ }
287
+
288
+ return {
289
+ fullResponse,
290
+ errorMessage,
291
+ toolRoutingResult: null,
292
+ responseCacheHit,
293
+ durationMs,
294
+ directUsage,
295
+ }
296
+ }
@@ -12,7 +12,7 @@
12
12
  import path from 'node:path'
13
13
  import type { StructuredToolInterface } from '@langchain/core/tools'
14
14
  import type { AppSettings, MessageToolEvent, SSEEvent } from '@/types'
15
- import { loadAgents } from '@/lib/server/storage'
15
+ import { getAgent } from '@/lib/server/agents/agent-repository'
16
16
  import { buildSessionTools } from '@/lib/server/session-tools'
17
17
  import { resolveConcreteToolPolicyBlock, type ExtensionPolicyDecision } from '@/lib/server/tool-capability-policy'
18
18
  import { resolveActiveProjectContext } from '@/lib/server/project-context'
@@ -350,7 +350,7 @@ export function resolveRequestedToolPreflightResponse(params: {
350
350
  const requestedToolNames = requestedToolNamesFromMessage(params.message)
351
351
  if (requestedToolNames.length === 0) return null
352
352
 
353
- const agent = params.session?.agentId ? loadAgents()[params.session.agentId] : null
353
+ const agent = params.session?.agentId ? getAgent(params.session.agentId) : null
354
354
  const blockedResponses: string[] = []
355
355
  const unavailableResponses: string[] = []
356
356
  for (const toolName of requestedToolNames) {
@@ -361,8 +361,8 @@ export function resolveRequestedToolPreflightResponse(params: {
361
361
  }
362
362
  if (
363
363
  (toolName === 'delegate' || toolName.startsWith('delegate_to_'))
364
- && agent
365
- && agent.delegationEnabled !== true
364
+ && params.session?.agentId
365
+ && agent?.delegationEnabled !== true
366
366
  ) {
367
367
  unavailableResponses.push(buildToolUnavailableResponse(toolName, 'delegation is not enabled for this agent right now'))
368
368
  continue
@@ -407,7 +407,7 @@ async function invokeSessionTool(
407
407
  return { invoked: false, responseOverride: null }
408
408
  }
409
409
 
410
- const agent = ctx.session.agentId ? loadAgents()[ctx.session.agentId] : null
410
+ const agent = ctx.session.agentId ? getAgent(ctx.session.agentId) : null
411
411
  const agentRecord = agent as Record<string, unknown> | null
412
412
  const activeProjectContext = resolveActiveProjectContext(ctx.session as unknown as { agentId?: string | null; cwd?: string | null; projectId?: string | null })
413
413
  const { tools, cleanup } = await buildSessionTools(ctx.session.cwd, ctx.enabledExtensions, {
@@ -143,9 +143,10 @@ function checkLoopDetection(ctx: ContinuationContext): ContinuationDecision | nu
143
143
  }
144
144
  }
145
145
 
146
- // Terminal — caller should break
147
- const errMessage = ctx.state.loopDetectionTriggered?.message || 'Tool frequency limit exceeded.'
148
- ctx.write(`data: ${JSON.stringify({ t: 'err', text: errMessage })}\n\n`) // err, not status
146
+ // Terminal — caller should break.
147
+ // Emit a user-friendly message instead of the raw diagnostic (which is internal).
148
+ // The structured diagnostic data is already carried via the `status` event in iteration-event-handler.
149
+ ctx.write(`data: ${JSON.stringify({ t: 'err', text: 'The agent got stuck in a repetitive loop and has been stopped. Please try rephrasing your request or breaking it into smaller steps.' })}\n\n`)
149
150
  return { type: false, requiredToolReminderNames: [] }
150
151
  }
151
152
 
@@ -54,7 +54,7 @@ const MAX_COORDINATOR_SYNTHESIS = 3
54
54
  const MAX_COORDINATOR_DELEGATION_NUDGE = 1
55
55
 
56
56
  /** Max loop recovery continuations (tool_frequency limit resets) */
57
- const MAX_LOOP_RECOVERY = 2
57
+ const MAX_LOOP_RECOVERY = 1
58
58
 
59
59
  /** Max context overflow retries (emergency context reduction) */
60
60
  const MAX_CONTEXT_OVERFLOW = 2
@@ -64,7 +64,7 @@ const MAX_CONTEXT_OVERFLOW = 2
64
64
  export class ContinuationLimits {
65
65
  private readonly limits: Record<BudgetedContinuation, LimitEntry>
66
66
 
67
- constructor(isConnectorSession: boolean) {
67
+ constructor(isConnectorSession: boolean, isHeartbeat = false) {
68
68
  let maxDeliverableFollowthroughs = MAX_DELIVERABLE_FOLLOWTHROUGH
69
69
  let maxExecutionFollowthroughs = MAX_EXECUTION_FOLLOWTHROUGH
70
70
  let maxAttachmentFollowthroughs = MAX_ATTACHMENT_FOLLOWTHROUGH
@@ -79,6 +79,9 @@ export class ContinuationLimits {
79
79
  maxUnfinishedToolFollowthroughs = 1
80
80
  }
81
81
 
82
+ // Heartbeats should not need loop recovery — they are brief status checks
83
+ const maxLoopRecovery = isHeartbeat ? 0 : MAX_LOOP_RECOVERY
84
+
82
85
  this.limits = {
83
86
  recursion: { count: 0, max: MAX_RECURSION },
84
87
  transient: { count: 0, max: MAX_TRANSIENT },
@@ -94,7 +97,7 @@ export class ContinuationLimits {
94
97
  tool_summary: { count: 0, max: maxToolSummaryRetries },
95
98
  coordinator_synthesis: { count: 0, max: MAX_COORDINATOR_SYNTHESIS },
96
99
  coordinator_delegation_nudge: { count: 0, max: MAX_COORDINATOR_DELEGATION_NUDGE },
97
- loop_recovery: { count: 0, max: MAX_LOOP_RECOVERY },
100
+ loop_recovery: { count: 0, max: maxLoopRecovery },
98
101
  }
99
102
  }
100
103