@swarmclawai/swarmclaw 1.2.1 → 1.2.3

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 (149) hide show
  1. package/README.md +16 -85
  2. package/bin/server-cmd.js +64 -1
  3. package/package.json +2 -2
  4. package/skills/coding-agent/SKILL.md +111 -0
  5. package/skills/github/SKILL.md +140 -0
  6. package/skills/nano-banana-pro/SKILL.md +62 -0
  7. package/skills/nano-banana-pro/scripts/generate_image.py +235 -0
  8. package/skills/nano-pdf/SKILL.md +53 -0
  9. package/skills/openai-image-gen/SKILL.md +78 -0
  10. package/skills/openai-image-gen/scripts/gen.py +328 -0
  11. package/skills/resourceful-problem-solving/SKILL.md +49 -0
  12. package/skills/skill-creator/SKILL.md +147 -0
  13. package/skills/skill-creator/scripts/init_skill.py +378 -0
  14. package/skills/skill-creator/scripts/quick_validate.py +159 -0
  15. package/skills/summarize/SKILL.md +77 -0
  16. package/src/app/api/auth/route.ts +20 -5
  17. package/src/app/api/chats/[id]/devserver/route.ts +13 -19
  18. package/src/app/api/chats/[id]/messages/route.ts +13 -15
  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/ip/route.ts +2 -2
  24. package/src/app/api/preview-server/route.ts +1 -1
  25. package/src/app/api/projects/[id]/route.ts +7 -46
  26. package/src/cli/server-cmd.test.js +74 -0
  27. package/src/components/chat/chat-area.tsx +45 -23
  28. package/src/components/chat/message-bubble.test.ts +35 -0
  29. package/src/components/chat/message-bubble.tsx +19 -9
  30. package/src/components/chat/message-list.tsx +37 -3
  31. package/src/components/input/chat-input.tsx +34 -14
  32. package/src/components/openclaw/openclaw-deploy-panel.tsx +4 -0
  33. package/src/instrumentation.ts +1 -1
  34. package/src/lib/chat/assistant-render-id.ts +3 -0
  35. package/src/lib/chat/chat-streaming-state.test.ts +42 -3
  36. package/src/lib/chat/chat-streaming-state.ts +20 -8
  37. package/src/lib/chat/queued-message-queue.test.ts +23 -1
  38. package/src/lib/chat/queued-message-queue.ts +11 -2
  39. package/src/lib/providers/cli-utils.test.ts +124 -0
  40. package/src/lib/server/activity/activity-log.ts +21 -0
  41. package/src/lib/server/agents/agent-availability.test.ts +10 -5
  42. package/src/lib/server/agents/agent-cascade.ts +79 -59
  43. package/src/lib/server/agents/agent-registry.ts +3 -1
  44. package/src/lib/server/agents/agent-repository.ts +90 -0
  45. package/src/lib/server/agents/delegation-job-repository.ts +53 -0
  46. package/src/lib/server/agents/delegation-jobs.ts +11 -4
  47. package/src/lib/server/agents/guardian-checkpoint-repository.ts +35 -0
  48. package/src/lib/server/agents/guardian.ts +2 -2
  49. package/src/lib/server/agents/main-agent-loop.ts +10 -3
  50. package/src/lib/server/agents/main-loop-state-repository.ts +38 -0
  51. package/src/lib/server/agents/subagent-runtime.ts +9 -6
  52. package/src/lib/server/agents/subagent-swarm.ts +3 -2
  53. package/src/lib/server/agents/task-session.ts +3 -4
  54. package/src/lib/server/approvals/approval-repository.ts +30 -0
  55. package/src/lib/server/autonomy/supervisor-incident-repository.ts +42 -0
  56. package/src/lib/server/chat-execution/chat-execution-types.ts +38 -0
  57. package/src/lib/server/chat-execution/chat-execution-utils.ts +1 -1
  58. package/src/lib/server/chat-execution/chat-execution.ts +84 -1926
  59. package/src/lib/server/chat-execution/chat-turn-finalization.ts +620 -0
  60. package/src/lib/server/chat-execution/chat-turn-partial-persistence.ts +221 -0
  61. package/src/lib/server/chat-execution/chat-turn-preflight.ts +133 -0
  62. package/src/lib/server/chat-execution/chat-turn-preparation.ts +817 -0
  63. package/src/lib/server/chat-execution/chat-turn-stream-execution.ts +296 -0
  64. package/src/lib/server/chat-execution/chat-turn-tool-routing.ts +5 -5
  65. package/src/lib/server/chat-execution/message-classifier.test.ts +329 -0
  66. package/src/lib/server/chat-execution/post-stream-finalization.ts +1 -1
  67. package/src/lib/server/chat-execution/prompt-builder.ts +11 -0
  68. package/src/lib/server/chat-execution/prompt-sections.ts +5 -6
  69. package/src/lib/server/chat-execution/situational-awareness.ts +12 -7
  70. package/src/lib/server/chat-execution/stream-agent-chat.ts +16 -13
  71. package/src/lib/server/chatrooms/chatroom-repository.ts +32 -0
  72. package/src/lib/server/connectors/connector-repository.ts +58 -0
  73. package/src/lib/server/connectors/runtime-state.test.ts +117 -0
  74. package/src/lib/server/credentials/credential-repository.ts +7 -0
  75. package/src/lib/server/gateways/gateway-profile-repository.ts +4 -0
  76. package/src/lib/server/memory/memory-abstract.test.ts +59 -0
  77. package/src/lib/server/missions/mission-repository.ts +74 -0
  78. package/src/lib/server/missions/mission-service/actions.ts +6 -0
  79. package/src/lib/server/missions/mission-service/bindings.ts +9 -0
  80. package/src/lib/server/missions/mission-service/context.ts +4 -0
  81. package/src/lib/server/missions/mission-service/core.ts +2269 -0
  82. package/src/lib/server/missions/mission-service/queries.ts +12 -0
  83. package/src/lib/server/missions/mission-service/recovery.ts +5 -0
  84. package/src/lib/server/missions/mission-service/ticks.ts +9 -0
  85. package/src/lib/server/missions/mission-service.test.ts +9 -2
  86. package/src/lib/server/missions/mission-service.ts +6 -2266
  87. package/src/lib/server/openclaw/deploy.test.ts +42 -3
  88. package/src/lib/server/openclaw/deploy.ts +26 -12
  89. package/src/lib/server/persistence/repository-utils.ts +154 -0
  90. package/src/lib/server/persistence/storage-context.ts +51 -0
  91. package/src/lib/server/persistence/transaction.ts +1 -0
  92. package/src/lib/server/projects/project-repository.ts +36 -0
  93. package/src/lib/server/projects/project-service.ts +79 -0
  94. package/src/lib/server/protocols/protocol-normalization.test.ts +6 -4
  95. package/src/lib/server/runtime/alert-dispatch.ts +1 -1
  96. package/src/lib/server/runtime/daemon-policy.ts +1 -1
  97. package/src/lib/server/runtime/daemon-state/core.ts +1570 -0
  98. package/src/lib/server/runtime/daemon-state/health.ts +6 -0
  99. package/src/lib/server/runtime/daemon-state/policy.ts +7 -0
  100. package/src/lib/server/runtime/daemon-state/supervisor.ts +6 -0
  101. package/src/lib/server/runtime/daemon-state.test.ts +48 -0
  102. package/src/lib/server/runtime/daemon-state.ts +3 -1470
  103. package/src/lib/server/runtime/estop-repository.ts +4 -0
  104. package/src/lib/server/runtime/estop.ts +3 -1
  105. package/src/lib/server/runtime/heartbeat-service.test.ts +2 -2
  106. package/src/lib/server/runtime/heartbeat-service.ts +55 -34
  107. package/src/lib/server/runtime/heartbeat-wake.ts +6 -4
  108. package/src/lib/server/runtime/idle-window.ts +2 -2
  109. package/src/lib/server/runtime/network.ts +11 -0
  110. package/src/lib/server/runtime/orchestrator-events.ts +2 -2
  111. package/src/lib/server/runtime/queue/claims.ts +4 -0
  112. package/src/lib/server/runtime/queue/core.ts +2079 -0
  113. package/src/lib/server/runtime/queue/execution.ts +7 -0
  114. package/src/lib/server/runtime/queue/followups.ts +4 -0
  115. package/src/lib/server/runtime/queue/queries.ts +12 -0
  116. package/src/lib/server/runtime/queue/recovery.ts +7 -0
  117. package/src/lib/server/runtime/queue-recovery.test.ts +48 -13
  118. package/src/lib/server/runtime/queue-repository.ts +17 -0
  119. package/src/lib/server/runtime/queue.ts +5 -2061
  120. package/src/lib/server/runtime/run-ledger.ts +6 -5
  121. package/src/lib/server/runtime/run-repository.ts +73 -0
  122. package/src/lib/server/runtime/runtime-lock-repository.ts +8 -0
  123. package/src/lib/server/runtime/runtime-settings.ts +1 -1
  124. package/src/lib/server/runtime/runtime-state.ts +99 -0
  125. package/src/lib/server/runtime/scheduler.ts +4 -2
  126. package/src/lib/server/runtime/session-run-manager/cancellation.ts +157 -0
  127. package/src/lib/server/runtime/session-run-manager/drain.ts +246 -0
  128. package/src/lib/server/runtime/session-run-manager/enqueue.ts +287 -0
  129. package/src/lib/server/runtime/session-run-manager/queries.ts +117 -0
  130. package/src/lib/server/runtime/session-run-manager/recovery.ts +238 -0
  131. package/src/lib/server/runtime/session-run-manager/state.ts +441 -0
  132. package/src/lib/server/runtime/session-run-manager/types.ts +74 -0
  133. package/src/lib/server/runtime/session-run-manager.ts +72 -1377
  134. package/src/lib/server/runtime/watch-job-repository.ts +35 -0
  135. package/src/lib/server/runtime/watch-jobs.ts +3 -1
  136. package/src/lib/server/schedules/schedule-repository.ts +42 -0
  137. package/src/lib/server/sessions/session-repository.ts +85 -0
  138. package/src/lib/server/settings/settings-repository.ts +25 -0
  139. package/src/lib/server/skills/skill-discovery.test.ts +2 -2
  140. package/src/lib/server/skills/skill-discovery.ts +2 -2
  141. package/src/lib/server/skills/skill-repository.ts +14 -0
  142. package/src/lib/server/storage.ts +13 -24
  143. package/src/lib/server/tasks/task-repository.ts +54 -0
  144. package/src/lib/server/usage/usage-repository.ts +30 -0
  145. package/src/lib/server/webhooks/webhook-repository.ts +10 -0
  146. package/src/lib/strip-internal-metadata.test.ts +42 -41
  147. package/src/stores/use-chat-store.test.ts +54 -0
  148. package/src/stores/use-chat-store.ts +21 -5
  149. /package/{bundled-skills → skills}/google-workspace/SKILL.md +0 -0
@@ -0,0 +1,4 @@
1
+ export {
2
+ loadPersistedEstopState,
3
+ savePersistedEstopState,
4
+ } from '@/lib/server/storage'
@@ -1,5 +1,7 @@
1
1
  import type { ApprovalRequest, EstopState } from '@/types'
2
- import { loadApprovals, loadPersistedEstopState, loadSettings, savePersistedEstopState, upsertApproval } from '@/lib/server/storage'
2
+ import { loadApprovals, upsertApproval } from '@/lib/server/approvals/approval-repository'
3
+ import { loadPersistedEstopState, savePersistedEstopState } from '@/lib/server/runtime/estop-repository'
4
+ import { loadSettings } from '@/lib/server/settings/settings-repository'
3
5
  import { requestApproval } from '@/lib/server/approvals'
4
6
 
5
7
  const DEFAULT_ESTOP_STATE: EstopState = {
@@ -370,8 +370,8 @@ describe('buildAgentHeartbeatPrompt', () => {
370
370
  {
371
371
  id: 's1',
372
372
  messages: [
373
- { role: 'user', text: 'Check the logs', toolEvents: [] },
374
- { role: 'assistant', text: 'Logs look clean', toolEvents: [] },
373
+ { role: 'user', text: 'Check the logs', toolEvents: [], time: Date.now() },
374
+ { role: 'assistant', text: 'Logs look clean', toolEvents: [], time: Date.now() },
375
375
  ],
376
376
  },
377
377
  { name: 'Bot' },
@@ -6,7 +6,13 @@ import {
6
6
  DEFAULT_HEARTBEAT_SHOW_ALERTS,
7
7
  DEFAULT_HEARTBEAT_SHOW_OK,
8
8
  } from '@/lib/runtime/heartbeat-defaults'
9
- import { loadAgents, loadApprovals, loadSessions, loadSettings, patchSession, patchAgent, loadChatrooms, loadMission } from '@/lib/server/storage'
9
+ import { logActivity } from '@/lib/server/activity/activity-log'
10
+ import { loadApprovals } from '@/lib/server/approvals/approval-repository'
11
+ import { loadAgents, patchAgent } from '@/lib/server/agents/agent-repository'
12
+ import { loadChatrooms } from '@/lib/server/chatrooms/chatroom-repository'
13
+ import { loadMission } from '@/lib/server/missions/mission-repository'
14
+ import { loadSessions, patchSession } from '@/lib/server/sessions/session-repository'
15
+ import { loadSettings } from '@/lib/server/settings/settings-repository'
10
16
  import { buildGoalAncestrySection, buildPlatformStatusSummary } from '@/lib/server/chat-execution/situational-awareness'
11
17
  import { drainDeferredWakes, hasDeferredWakes } from '@/lib/server/runtime/wake-dispatcher'
12
18
  import { buildWakeTriggerContext } from '@/lib/server/runtime/heartbeat-wake'
@@ -15,7 +21,7 @@ import { log } from '@/lib/server/logger'
15
21
  import { WORKSPACE_DIR } from '@/lib/server/data-dir'
16
22
  import { drainSystemEvents, drainOrchestratorEvents } from '@/lib/server/runtime/system-events'
17
23
  import { buildMissionContextBlock } from '@/lib/server/missions/mission-service'
18
- import type { Agent, Chatroom } from '@/types'
24
+ import type { Agent, AppSettings, ApprovalRequest, Chatroom, Message, Session } from '@/types'
19
25
  import { isOrchestratorEligible } from '@/lib/orchestrator-config'
20
26
  import { buildIdentityContinuityContext } from '@/lib/server/identity-continuity'
21
27
  import { buildMainLoopHeartbeatPrompt, getMainLoopStateForSession, isMainSession } from '@/lib/server/agents/main-agent-loop'
@@ -23,7 +29,6 @@ import { ensureAgentThreadSession } from '@/lib/server/agents/agent-thread-sessi
23
29
  import { isAgentDisabled } from '@/lib/server/agents/agent-availability'
24
30
  import { errorMessage, hmrSingleton, jitteredBackoff } from '@/lib/shared-utils'
25
31
  import { logExecution } from '@/lib/server/execution-log'
26
- import { logActivity } from '@/lib/server/storage'
27
32
  import { createNotification } from '@/lib/server/create-notification'
28
33
  import { WORKER_ONLY_PROVIDER_IDS } from '@/lib/provider-sets'
29
34
 
@@ -190,6 +195,10 @@ interface HeartbeatFileSession {
190
195
  cwd?: string | null
191
196
  }
192
197
 
198
+ type HeartbeatPromptSession = Partial<Session> & Record<string, unknown>
199
+ type HeartbeatPromptAgent = Partial<Agent>
200
+ type HeartbeatPromptMessage = Pick<Message, 'role' | 'text' | 'time' | 'toolEvents'>
201
+
193
202
  const DEFAULT_HEARTBEAT_PROMPT = 'Read HEARTBEAT.md if it exists (workspace context). Follow it strictly. Do not infer or repeat old tasks from prior chats. If nothing needs attention, reply HEARTBEAT_OK.'
194
203
 
195
204
  export function readHeartbeatFile(session: HeartbeatFileSession): string {
@@ -202,18 +211,24 @@ export function readHeartbeatFile(session: HeartbeatFileSession): string {
202
211
  return ''
203
212
  }
204
213
 
205
- const identityFileCache = hmrSingleton<Map<string, { data: Record<string, string>; expiresAt: number }>>(
214
+ const identityFileCache = hmrSingleton<Map<string, { data: Record<string, string>; expiresAt: number; mtimeMs: number | null }>>(
206
215
  '__hb_identity_cache__', () => new Map(),
207
216
  )
208
217
  const IDENTITY_CACHE_TTL_MS = 60_000
209
218
 
210
219
  function readIdentityFile(session: { cwd?: string | null }): Record<string, string> {
211
220
  const cwd = typeof session.cwd === 'string' ? session.cwd : WORKSPACE_DIR
221
+ const filePath = path.join(cwd, 'IDENTITY.md')
222
+ let mtimeMs: number | null = null
223
+ try {
224
+ mtimeMs = fs.statSync(filePath).mtimeMs
225
+ } catch {
226
+ mtimeMs = null
227
+ }
212
228
  const cached = identityFileCache.get(cwd)
213
- if (cached && Date.now() < cached.expiresAt) return cached.data
229
+ if (cached && Date.now() < cached.expiresAt && cached.mtimeMs === mtimeMs) return cached.data
214
230
  try {
215
- const filePath = path.join(cwd, 'IDENTITY.md')
216
- if (fs.existsSync(filePath)) {
231
+ if (mtimeMs !== null) {
217
232
  const content = fs.readFileSync(filePath, 'utf-8')
218
233
  const identity: Record<string, string> = {}
219
234
  for (const line of content.split('\n')) {
@@ -224,12 +239,12 @@ function readIdentityFile(session: { cwd?: string | null }): Record<string, stri
224
239
  const value = cleaned.slice(colonIndex + 1).replace(/^[*_]+|[*_]+$/g, '').trim()
225
240
  if (value) identity[label] = value
226
241
  }
227
- identityFileCache.set(cwd, { data: identity, expiresAt: Date.now() + IDENTITY_CACHE_TTL_MS })
242
+ identityFileCache.set(cwd, { data: identity, expiresAt: Date.now() + IDENTITY_CACHE_TTL_MS, mtimeMs })
228
243
  return identity
229
244
  }
230
245
  } catch { /* ignore */ }
231
246
  const empty: Record<string, string> = {}
232
- identityFileCache.set(cwd, { data: empty, expiresAt: Date.now() + IDENTITY_CACHE_TTL_MS })
247
+ identityFileCache.set(cwd, { data: empty, expiresAt: Date.now() + IDENTITY_CACHE_TTL_MS, mtimeMs: null })
233
248
  return empty
234
249
  }
235
250
 
@@ -300,11 +315,11 @@ export function isHeartbeatContentEffectivelyEmpty(content: string | undefined |
300
315
  }
301
316
 
302
317
  export function buildAgentHeartbeatPrompt(
303
- session: any,
304
- agent: any,
318
+ session: HeartbeatPromptSession,
319
+ agent: HeartbeatPromptAgent | null | undefined,
305
320
  fallbackPrompt: string,
306
321
  heartbeatFileContent: string,
307
- opts?: { approvals?: Record<string, unknown>; chatrooms?: Record<string, unknown> },
322
+ opts?: { approvals?: Record<string, ApprovalRequest>; chatrooms?: Record<string, Chatroom> },
308
323
  ): string {
309
324
  if (!agent) return fallbackPrompt
310
325
 
@@ -342,12 +357,12 @@ export function buildAgentHeartbeatPrompt(
342
357
  }
343
358
 
344
359
  // ── Phase 3: Goal ancestry ──
345
- const missionId = session.missionId || agent.missionId || null
360
+ const missionId = (session.missionId || (agent as Record<string, unknown>).missionId || null) as string | null
346
361
  const goalAncestry = buildGoalAncestrySection(missionId)
347
362
  if (goalAncestry) sections.push(goalAncestry)
348
363
 
349
364
  // ── Phase 4: Active task checkout & events ──
350
- const events = drainSystemEvents(session.id)
365
+ const events = drainSystemEvents(session.id!)
351
366
  if (events.length > 0) {
352
367
  const eventBlock = events.map((e) => `- [${new Date(e.timestamp).toISOString()}] ${e.text}`).join('\n')
353
368
  sections.push(`Events since last heartbeat:\n${eventBlock}`)
@@ -369,9 +384,9 @@ export function buildAgentHeartbeatPrompt(
369
384
  const effectiveFileContent = isHeartbeatContentEffectivelyEmpty(strippedContent) ? '' : strippedContent
370
385
  if (effectiveFileContent) sections.push(`\nHEARTBEAT.md contents:\n${effectiveFileContent.slice(0, 2000)}`)
371
386
 
372
- const recentMessages = (session.messages || []).slice(-5)
387
+ const recentMessages = (Array.isArray(session.messages) ? session.messages : []).slice(-5) as HeartbeatPromptMessage[]
373
388
  const recentContext = recentMessages
374
- .map((m: any) => {
389
+ .map((m) => {
375
390
  const text = (m.text || '').slice(0, 200)
376
391
  const tools = Array.isArray(m.toolEvents) && m.toolEvents.length > 0
377
392
  ? ` [tools used: ${m.toolEvents.map((t: { name: string }) => t.name).join(', ')}]`
@@ -386,7 +401,7 @@ export function buildAgentHeartbeatPrompt(
386
401
  const chatrooms = Object.values(opts?.chatrooms ?? loadChatrooms()) as Chatroom[]
387
402
  const myChatrooms = chatrooms.filter((c) => !c.archivedAt && c.agentIds?.includes(agentId))
388
403
  if (myChatrooms.length > 0) {
389
- const lastHeartbeat = state.lastBySession.get(session.id) || 0
404
+ const lastHeartbeat = state.lastBySession.get(session.id!) || 0
390
405
  const chatroomLines = myChatrooms
391
406
  .map((c) => {
392
407
  const recent = (c.messages || []).filter((m: { time: number }) => m.time > lastHeartbeat)
@@ -422,36 +437,42 @@ export function buildAgentHeartbeatPrompt(
422
437
  return sections.filter(Boolean).join('\n')
423
438
  }
424
439
 
425
- function resolveInterval(obj: Record<string, any>, currentSec: number): number {
440
+ function resolveInterval(obj: object, currentSec: number): number {
441
+ const r = obj as Record<string, unknown>
426
442
  // Prefer heartbeatInterval (duration string) over heartbeatIntervalSec (raw number)
427
- if (obj.heartbeatInterval !== undefined && obj.heartbeatInterval !== null) {
428
- return parseDuration(obj.heartbeatInterval, currentSec)
443
+ if (r.heartbeatInterval !== undefined && r.heartbeatInterval !== null) {
444
+ return parseDuration(r.heartbeatInterval, currentSec)
429
445
  }
430
- if (obj.heartbeatIntervalSec !== undefined && obj.heartbeatIntervalSec !== null) {
431
- return parseIntBounded(obj.heartbeatIntervalSec, currentSec, 0, 86400)
446
+ if (r.heartbeatIntervalSec !== undefined && r.heartbeatIntervalSec !== null) {
447
+ return parseIntBounded(r.heartbeatIntervalSec, currentSec, 0, 86400)
432
448
  }
433
449
  return currentSec
434
450
  }
435
451
 
436
- function resolveStr(obj: Record<string, any>, key: string, current: string | null): string | null {
437
- const val = obj[key]
452
+ function resolveStr(obj: object, key: string, current: string | null): string | null {
453
+ const val = (obj as Record<string, unknown>)[key]
438
454
  if (typeof val === 'string' && val.trim()) return val.trim()
439
455
  return current
440
456
  }
441
457
 
442
- function resolveBool(obj: Record<string, any>, key: string, current: boolean): boolean {
443
- if (obj[key] === true) return true
444
- if (obj[key] === false) return false
458
+ function resolveBool(obj: object, key: string, current: boolean): boolean {
459
+ const r = obj as Record<string, unknown>
460
+ if (r[key] === true) return true
461
+ if (r[key] === false) return false
445
462
  return current
446
463
  }
447
464
 
448
- function resolveNum(obj: Record<string, any>, key: string, current: number): number {
449
- const val = obj[key]
465
+ function resolveNum(obj: object, key: string, current: number): number {
466
+ const val = (obj as Record<string, unknown>)[key]
450
467
  if (typeof val === 'number' && Number.isFinite(val)) return Math.trunc(val)
451
468
  return current
452
469
  }
453
470
 
454
- export function heartbeatConfigForSession(session: any, settings: Record<string, any>, agents: Record<string, any>): HeartbeatConfig {
471
+ export function heartbeatConfigForSession(
472
+ session: HeartbeatPromptSession,
473
+ settings: Partial<AppSettings>,
474
+ agents: Record<string, HeartbeatPromptAgent>,
475
+ ): HeartbeatConfig {
455
476
  // Global defaults — 30 min interval (was 120s)
456
477
  let intervalSec = resolveInterval(settings, DEFAULT_HEARTBEAT_INTERVAL_SEC)
457
478
  const globalPrompt = (typeof settings.heartbeatPrompt === 'string' && settings.heartbeatPrompt.trim())
@@ -498,7 +519,7 @@ export function heartbeatConfigForSession(session: any, settings: Record<string,
498
519
  return { enabled: enabled && intervalSec > 0, intervalSec, prompt, model, ackMaxChars, showOk, showAlerts, target, lightContext }
499
520
  }
500
521
 
501
- function lastUserMessageAt(session: any): number {
522
+ function lastUserMessageAt(session: HeartbeatPromptSession): number {
502
523
  if (!Array.isArray(session?.messages)) return 0
503
524
  for (let i = session.messages.length - 1; i >= 0; i--) {
504
525
  const msg = session.messages[i]
@@ -509,15 +530,15 @@ function lastUserMessageAt(session: any): number {
509
530
  return 0
510
531
  }
511
532
 
512
- function resolveHeartbeatUserIdleSec(settings: Record<string, any>, fallbackSec: number): number {
513
- const configured = settings.heartbeatUserIdleSec
533
+ function resolveHeartbeatUserIdleSec(settings: Partial<AppSettings>, fallbackSec: number): number {
534
+ const configured = (settings as Record<string, unknown>).heartbeatUserIdleSec
514
535
  if (configured === undefined || configured === null || configured === '') {
515
536
  return fallbackSec
516
537
  }
517
538
  return parseIntBounded(configured, fallbackSec, 0, 86_400)
518
539
  }
519
540
 
520
- function shouldRunHeartbeats(settings: Record<string, any>): boolean {
541
+ function shouldRunHeartbeats(settings: Partial<AppSettings>): boolean {
521
542
  const loopMode = settings.loopMode === 'ongoing' ? 'ongoing' : 'bounded'
522
543
  return loopMode === 'ongoing'
523
544
  }
@@ -12,7 +12,9 @@ import {
12
12
  readHeartbeatFile,
13
13
  } from '@/lib/server/runtime/heartbeat-service'
14
14
  import { buildMainLoopHeartbeatPrompt, isMainSession } from '@/lib/server/agents/main-agent-loop'
15
- import { loadSessions, loadAgents, loadSettings } from '@/lib/server/storage'
15
+ import { listAgents } from '@/lib/server/agents/agent-repository'
16
+ import { getSession, listSessions } from '@/lib/server/sessions/session-repository'
17
+ import { loadSettings } from '@/lib/server/settings/settings-repository'
16
18
  import {
17
19
  enqueueSessionRun,
18
20
  getSessionExecutionState,
@@ -314,9 +316,9 @@ function flushWakes(): void {
314
316
 
315
317
  if (!wakes.length) return
316
318
 
317
- const agents = loadAgents()
319
+ const agents = listAgents()
318
320
  const settings = loadSettings()
319
- const sessions = loadSessions() as unknown as Record<string, Record<string, unknown>>
321
+ const sessions = listSessions() as unknown as Record<string, Record<string, unknown>>
320
322
  let delayedForRetry = false
321
323
 
322
324
  for (const wake of wakes) {
@@ -324,7 +326,7 @@ function flushWakes(): void {
324
326
  const sessionId = resolveWakeSessionId(wake, sessions)
325
327
  if (!sessionId) continue
326
328
 
327
- const session = (sessions[sessionId] || loadSessions()[sessionId]) as unknown as Record<string, unknown> | undefined
329
+ const session = (sessions[sessionId] || getSession(sessionId)) as unknown as Record<string, unknown> | undefined
328
330
  if (!session) continue
329
331
 
330
332
  let execution = getSessionExecutionState(sessionId)
@@ -1,4 +1,4 @@
1
- import { loadSessions } from '@/lib/server/storage'
1
+ import { listSessions } from '@/lib/server/sessions/session-repository'
2
2
  import type { Session } from '@/types'
3
3
  import { log } from '@/lib/server/logger'
4
4
 
@@ -26,7 +26,7 @@ const state: IdleWindowState = {
26
26
  export async function isIdleWindow(options?: { thresholdMs?: number }): Promise<boolean> {
27
27
  const threshold = options?.thresholdMs ?? DEFAULT_IDLE_THRESHOLD_MS
28
28
  const now = Date.now()
29
- const sessions = loadSessions()
29
+ const sessions = listSessions()
30
30
 
31
31
  for (const session of Object.values(sessions) as unknown as Session[]) {
32
32
  if (!session?.id) continue
@@ -0,0 +1,11 @@
1
+ import os from 'os'
2
+
3
+ export function localIP(): string {
4
+ for (const interfaces of Object.values(os.networkInterfaces())) {
5
+ if (!interfaces) continue
6
+ for (const network of interfaces) {
7
+ if (network.family === 'IPv4' && !network.internal) return network.address
8
+ }
9
+ }
10
+ return 'localhost'
11
+ }
@@ -1,6 +1,6 @@
1
1
  import { enqueueOrchestratorEvent } from '@/lib/server/runtime/system-events'
2
2
  import { isOrchestratorEligible } from '@/lib/orchestrator-config'
3
- import { loadAgents } from '@/lib/server/storage'
3
+ import { listAgents } from '@/lib/server/agents/agent-repository'
4
4
 
5
5
  /**
6
6
  * Broadcast an event to all orchestrator-enabled agents.
@@ -8,7 +8,7 @@ import { loadAgents } from '@/lib/server/storage'
8
8
  */
9
9
  export function notifyOrchestrators(text: string, contextKey?: string): void {
10
10
  try {
11
- const agents = loadAgents()
11
+ const agents = listAgents()
12
12
  for (const agent of Object.values(agents)) {
13
13
  if (agent.orchestratorEnabled && !agent.disabled && !agent.trashedAt && isOrchestratorEligible(agent)) {
14
14
  enqueueOrchestratorEvent(agent.id, text, contextKey)
@@ -0,0 +1,4 @@
1
+ export {
2
+ claimPoolTask,
3
+ listClaimableTasks,
4
+ } from './core'