@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
@@ -3,6 +3,9 @@ import { execSync } from 'child_process'
3
3
  import { loadSessions } from '@/lib/server/storage'
4
4
  import { notFound } from '@/lib/server/collection-helpers'
5
5
  import { safeParseBody } from '@/lib/server/safe-parse-body'
6
+ import { log } from '@/lib/server/logger'
7
+
8
+ const TAG = 'api-deploy'
6
9
 
7
10
  export async function POST(req: Request, { params }: { params: Promise<{ id: string }> }) {
8
11
  const { id } = await params
@@ -21,16 +24,18 @@ export async function POST(req: Request, { params }: { params: Promise<{ id: str
21
24
  try {
22
25
  execSync(`git commit -m "${msg.replace(/"/g, '\\"')}"`, opts)
23
26
  committed = true
24
- } catch (ce: any) {
25
- if (!(ce.stdout || ce.stderr || '').includes('nothing to commit')) throw ce
27
+ } catch (ce: unknown) {
28
+ const ex = ce as { stdout?: string; stderr?: string }
29
+ if (!(ex.stdout || ex.stderr || '').includes('nothing to commit')) throw ce
26
30
  }
27
31
  execSync('git push 2>&1', opts)
28
- console.log(`[${id}] deployed: ${msg}`)
32
+ log.info(TAG, `deployed: ${msg}`)
29
33
  return NextResponse.json({ ok: true, output: committed ? 'Committed and pushed!' : 'Already committed — pushed to remote!' })
30
- } catch (e: any) {
31
- console.error(`[${id}] deploy error:`, e.message)
34
+ } catch (e: unknown) {
35
+ const ex = e as { stderr?: string; stdout?: string; message?: string }
36
+ log.error(TAG, `deploy error:`, ex.message)
32
37
  return NextResponse.json(
33
- { ok: false, error: (e.stderr || e.stdout || e.message).toString().slice(0, 300) },
38
+ { ok: false, error: (ex.stderr || ex.stdout || ex.message || 'Unknown error').toString().slice(0, 300) },
34
39
  { status: 500 },
35
40
  )
36
41
  }
@@ -1,11 +1,16 @@
1
1
  import { NextResponse } from 'next/server'
2
2
  import { spawn } from 'child_process'
3
- import { loadSessions, devServers, localIP } from '@/lib/server/storage'
4
3
  import { notFound } from '@/lib/server/collection-helpers'
5
4
  import { resolveDevServerLaunchDir } from '@/lib/server/runtime/devserver-launch'
5
+ import { clearDevServer, getDevServer, hasDevServer, registerDevServer, stopDevServer, updateDevServerUrl } from '@/lib/server/runtime/runtime-state'
6
+ import { localIP } from '@/lib/server/runtime/network'
7
+ import { listSessions } from '@/lib/server/sessions/session-repository'
6
8
  import { safeParseBody } from '@/lib/server/safe-parse-body'
7
9
  import { sleep } from '@/lib/shared-utils'
8
10
  import net from 'net'
11
+ import { log } from '@/lib/server/logger'
12
+
13
+ const TAG = 'api-devserver'
9
14
 
10
15
  interface DevServerStartResult {
11
16
  status?: number
@@ -53,22 +58,21 @@ async function startDevServer(id: string, session: { cwd: string }): Promise<Dev
53
58
  if (match) {
54
59
  const detectedPort = match[1]
55
60
  detectedUrl = `http://${localIP()}:${detectedPort}`
56
- const ds = devServers.get(id)
57
- if (ds) ds.url = detectedUrl
61
+ updateDevServerUrl(id, detectedUrl)
58
62
  }
59
63
  }
60
64
  }
61
65
 
62
66
  proc.stdout!.on('data', onData)
63
67
  proc.stderr!.on('data', onData)
64
- proc.on('close', () => { devServers.delete(id); console.log(`[${id}] dev server stopped`) })
65
- proc.on('error', () => devServers.delete(id))
68
+ proc.on('close', () => { clearDevServer(id); log.info(TAG, `dev server stopped for ${id}`) })
69
+ proc.on('error', () => clearDevServer(id))
66
70
 
67
- devServers.set(id, { proc, url: `http://${localIP()}:${port}` })
68
- console.log(`[${id}] starting dev server in ${launch.launchDir} (session cwd=${session.cwd})`)
71
+ registerDevServer(id, { proc, url: `http://${localIP()}:${port}` })
72
+ log.info(TAG, `starting dev server in ${launch.launchDir} (session cwd=${session.cwd})`)
69
73
 
70
74
  await sleep(4000)
71
- const ds = devServers.get(id)
75
+ const ds = getDevServer(id)
72
76
  if (!ds) {
73
77
  return {
74
78
  status: 502,
@@ -96,7 +100,7 @@ async function startDevServer(id: string, session: { cwd: string }): Promise<Dev
96
100
 
97
101
  export async function POST(req: Request, { params }: { params: Promise<{ id: string }> }) {
98
102
  const { id } = await params
99
- const sessions = loadSessions()
103
+ const sessions = listSessions()
100
104
  const session = sessions[id]
101
105
  if (!session) return notFound()
102
106
 
@@ -105,8 +109,8 @@ export async function POST(req: Request, { params }: { params: Promise<{ id: str
105
109
  const { action } = body
106
110
 
107
111
  if (action === 'start') {
108
- if (devServers.has(id)) {
109
- const ds = devServers.get(id)!
112
+ if (hasDevServer(id)) {
113
+ const ds = getDevServer(id)!
110
114
  return NextResponse.json({ running: true, url: ds.url })
111
115
  }
112
116
 
@@ -123,18 +127,11 @@ export async function POST(req: Request, { params }: { params: Promise<{ id: str
123
127
  return NextResponse.json(result.body, result.status ? { status: result.status } : undefined)
124
128
 
125
129
  } else if (action === 'stop') {
126
- if (devServers.has(id)) {
127
- const ds = devServers.get(id)!
128
- try { ds.proc.kill('SIGTERM') } catch {}
129
- if (typeof ds.proc.pid === 'number') {
130
- try { process.kill(-ds.proc.pid, 'SIGTERM') } catch {}
131
- }
132
- devServers.delete(id)
133
- }
130
+ stopDevServer(id)
134
131
  return NextResponse.json({ running: false })
135
132
 
136
133
  } else if (action === 'status') {
137
- return NextResponse.json({ running: devServers.has(id), url: devServers.get(id)?.url })
134
+ return NextResponse.json({ running: hasDevServer(id), url: getDevServer(id)?.url })
138
135
  }
139
136
 
140
137
  return NextResponse.json({ running: false })
@@ -1,21 +1,25 @@
1
1
  import { NextResponse } from 'next/server'
2
- import { loadStoredItem, upsertStoredItem } from '@/lib/server/storage'
3
2
  import { notFound } from '@/lib/server/collection-helpers'
4
3
  import { materializeStreamingAssistantArtifacts } from '@/lib/chat/chat-streaming-state'
5
4
  import { appendSessionNote } from '@/lib/server/session-note'
6
- import type { Message, Session } from '@/types'
5
+ import { getSessionRunState } from '@/lib/server/runtime/session-run-manager'
6
+ import { getSession, saveSession } from '@/lib/server/sessions/session-repository'
7
+ import type { Message } from '@/types'
7
8
  import { safeParseBody } from '@/lib/server/safe-parse-body'
8
9
 
9
10
  export async function GET(req: Request, { params }: { params: Promise<{ id: string }> }) {
10
11
  const { id } = await params
11
- const session = loadStoredItem('sessions', id) as Session | null
12
+ const session = getSession(id)
12
13
  if (!session) return notFound()
13
14
  session.messages = Array.isArray(session.messages) ? session.messages : []
14
15
 
16
+ // Use persisted fields plus the run ledger. Process-local execution state is
17
+ // intentionally excluded here so stale registry entries do not block cleanup.
15
18
  const sessionClaimsActive = session.active === true
16
19
  || (typeof session.currentRunId === 'string' && session.currentRunId.trim().length > 0)
20
+ || !!getSessionRunState(id).runningRunId
17
21
  if (!sessionClaimsActive && materializeStreamingAssistantArtifacts(session.messages)) {
18
- upsertStoredItem('sessions', id, session)
22
+ saveSession(id, session)
19
23
  }
20
24
 
21
25
  const url = new URL(req.url)
@@ -57,7 +61,7 @@ export async function POST(req: Request, { params }: { params: Promise<{ id: str
57
61
  if (error) return error
58
62
 
59
63
  if (body.kind === 'context-clear') {
60
- const session = loadStoredItem('sessions', id) as Session | null
64
+ const session = getSession(id)
61
65
  if (!session) return notFound()
62
66
 
63
67
  session.messages.push({
@@ -66,7 +70,7 @@ export async function POST(req: Request, { params }: { params: Promise<{ id: str
66
70
  kind: 'context-clear',
67
71
  time: Date.now(),
68
72
  })
69
- upsertStoredItem('sessions', id, session)
73
+ saveSession(id, session)
70
74
  return NextResponse.json({ ok: true })
71
75
  }
72
76
 
@@ -78,7 +82,7 @@ export async function POST(req: Request, { params }: { params: Promise<{ id: str
78
82
  kind: body.messageKind || 'system',
79
83
  })
80
84
  if (!inserted) {
81
- const session = loadStoredItem('sessions', id) as Session | null
85
+ const session = getSession(id)
82
86
  if (!session) return notFound()
83
87
  return NextResponse.json({ error: 'Note text is required' }, { status: 400 })
84
88
  }
@@ -92,7 +96,7 @@ export async function PUT(req: Request, { params }: { params: Promise<{ id: stri
92
96
  const { id } = await params
93
97
  const { data: body, error } = await safeParseBody<{ messageIndex: number; bookmarked: boolean }>(req)
94
98
  if (error) return error
95
- const session = loadStoredItem('sessions', id) as Session | null
99
+ const session = getSession(id)
96
100
  if (!session) return notFound()
97
101
 
98
102
  const { messageIndex, bookmarked } = body
@@ -101,7 +105,7 @@ export async function PUT(req: Request, { params }: { params: Promise<{ id: stri
101
105
  }
102
106
 
103
107
  session.messages[messageIndex].bookmarked = bookmarked
104
- upsertStoredItem('sessions', id, session)
108
+ saveSession(id, session)
105
109
  return NextResponse.json(session.messages[messageIndex])
106
110
  }
107
111
 
@@ -109,7 +113,7 @@ export async function DELETE(req: Request, { params }: { params: Promise<{ id: s
109
113
  const { id } = await params
110
114
  const { data: body, error } = await safeParseBody<{ messageIndex: number }>(req)
111
115
  if (error) return error
112
- const session = loadStoredItem('sessions', id) as Session | null
116
+ const session = getSession(id)
113
117
  if (!session) return notFound()
114
118
 
115
119
  const { messageIndex } = body
@@ -123,6 +127,6 @@ export async function DELETE(req: Request, { params }: { params: Promise<{ id: s
123
127
  }
124
128
 
125
129
  session.messages.splice(messageIndex, 1)
126
- upsertStoredItem('sessions', id, session)
130
+ saveSession(id, session)
127
131
  return NextResponse.json({ ok: true })
128
132
  }
@@ -1,10 +1,12 @@
1
1
  import { NextResponse } from 'next/server'
2
- import { loadSession, upsertSession, deleteSession, active, loadAgents } from '@/lib/server/storage'
2
+ import { loadAgents } from '@/lib/server/storage'
3
3
  import { notFound } from '@/lib/server/collection-helpers'
4
4
  import { normalizeProviderEndpoint } from '@/lib/openclaw/openclaw-endpoint'
5
5
  import { resolvePrimaryAgentRoute } from '@/lib/server/agents/agent-runtime-config'
6
6
  import { clearMainLoopStateForSession } from '@/lib/server/agents/main-agent-loop'
7
7
  import { cleanupSessionProcesses } from '@/lib/server/runtime/process-manager'
8
+ import { deleteSession, getSession, saveSession } from '@/lib/server/sessions/session-repository'
9
+ import { stopActiveSessionProcess } from '@/lib/server/runtime/runtime-state'
8
10
  import { getSessionQueueSnapshot, getSessionRunState } from '@/lib/server/runtime/session-run-manager'
9
11
  import { normalizeCapabilitySelection } from '@/lib/capability-selection'
10
12
  import { enrichSessionWithMissionSummary } from '@/lib/server/missions/mission-service'
@@ -12,12 +14,12 @@ import { safeParseBody } from '@/lib/server/safe-parse-body'
12
14
 
13
15
  export async function GET(_req: Request, { params }: { params: Promise<{ id: string }> }) {
14
16
  const { id } = await params
15
- const session = loadSession(id)
17
+ const session = getSession(id)
16
18
  if (!session) return notFound()
17
19
 
18
20
  const run = getSessionRunState(id)
19
21
  const queue = getSessionQueueSnapshot(id)
20
- session.active = active.has(id) || !!run.runningRunId
22
+ session.active = !!run.runningRunId
21
23
  session.queuedCount = queue.queueLength
22
24
  session.currentRunId = run.runningRunId || null
23
25
 
@@ -28,7 +30,7 @@ export async function PUT(req: Request, { params }: { params: Promise<{ id: stri
28
30
  const { id } = await params
29
31
  const { data: updates, error } = await safeParseBody(req)
30
32
  if (error) return error
31
- const session = loadSession(id) as Record<string, unknown> | null
33
+ const session = getSession(id) as Record<string, unknown> | null
32
34
  if (!session) return notFound()
33
35
 
34
36
  if (updates.resetMainLoopState === true) {
@@ -142,17 +144,14 @@ export async function PUT(req: Request, { params }: { params: Promise<{ id: stri
142
144
  if (updates.delegateResumeIds !== undefined) session.delegateResumeIds = updates.delegateResumeIds
143
145
  if (!Array.isArray(session.messages)) session.messages = []
144
146
 
145
- upsertSession(id, session)
147
+ saveSession(id, session)
146
148
  return NextResponse.json(enrichSessionWithMissionSummary(session as never))
147
149
  }
148
150
 
149
151
  export async function DELETE(_req: Request, { params }: { params: Promise<{ id: string }> }) {
150
152
  const { id } = await params
151
- if (!loadSession(id)) return notFound()
152
- if (active.has(id)) {
153
- try { active.get(id)?.kill() } catch {}
154
- active.delete(id)
155
- }
153
+ if (!getSession(id)) return notFound()
154
+ stopActiveSessionProcess(id)
156
155
  cleanupSessionProcesses(id)
157
156
  deleteSession(id)
158
157
  return new NextResponse('OK')
@@ -1,19 +1,17 @@
1
1
  import { NextResponse } from 'next/server'
2
2
  import { materializeStreamingAssistantArtifacts } from '@/lib/chat/chat-streaming-state'
3
- import { active, loadStoredItem, upsertStoredItem } from '@/lib/server/storage'
4
3
  import { cancelSessionRuns } from '@/lib/server/runtime/session-run-manager'
4
+ import { stopActiveSessionProcess } from '@/lib/server/runtime/runtime-state'
5
+ import { getSession, saveSession } from '@/lib/server/sessions/session-repository'
5
6
  import type { Session } from '@/types'
6
7
 
7
8
  export async function POST(_req: Request, { params }: { params: Promise<{ id: string }> }) {
8
9
  const { id } = await params
9
10
  const cancel = cancelSessionRuns(id, 'Stopped by user')
10
- const session = loadStoredItem('sessions', id) as Session | null
11
+ const session = getSession(id) as Session | null
11
12
  if (session && Array.isArray(session.messages) && materializeStreamingAssistantArtifacts(session.messages)) {
12
- upsertStoredItem('sessions', id, session)
13
- }
14
- if (active.has(id)) {
15
- try { active.get(id)?.kill() } catch {}
16
- active.delete(id)
13
+ saveSession(id, session)
17
14
  }
15
+ stopActiveSessionProcess(id)
18
16
  return NextResponse.json({ ok: true, ...cancel })
19
17
  }
@@ -10,11 +10,13 @@ test('chat messages route materializes stale streaming artifacts even if runtime
10
10
  returnedText: string | null
11
11
  persistedStreaming: boolean | null
12
12
  persistedText: string | null
13
- }>(`
14
- const storageMod = await import('./src/lib/server/storage')
15
- const routeMod = await import('./src/app/api/chats/[id]/messages/route')
16
- const storage = storageMod.default || storageMod
17
- const route = routeMod.default || routeMod
13
+ }>(`
14
+ const storageMod = await import('./src/lib/server/storage')
15
+ const routeMod = await import('./src/app/api/chats/[id]/messages/route')
16
+ const runtimeStateMod = await import('./src/lib/server/runtime/runtime-state')
17
+ const storage = storageMod.default || storageMod
18
+ const route = routeMod.default || routeMod
19
+ const runtimeState = runtimeStateMod.default || runtimeStateMod
18
20
 
19
21
  storage.upsertStoredItem('sessions', 'session-stale', {
20
22
  id: 'session-stale',
@@ -37,7 +39,7 @@ test('chat messages route materializes stale streaming artifacts even if runtime
37
39
  ],
38
40
  })
39
41
 
40
- storage.active.set('session-stale', { kill() {} })
42
+ runtimeState.registerActiveSessionProcess('session-stale', { kill() {} })
41
43
 
42
44
  const response = await route.GET(
43
45
  new Request('http://local/api/chats/session-stale/messages'),
@@ -3,9 +3,11 @@ import { genId } from '@/lib/id'
3
3
  import os from 'os'
4
4
  import path from 'path'
5
5
  import { perf } from '@/lib/server/runtime/perf'
6
- import { loadSessions, saveSessions, deleteSession, active, loadAgents } from '@/lib/server/storage'
6
+ import { loadAgents } from '@/lib/server/storage'
7
7
  import { WORKSPACE_DIR } from '@/lib/server/data-dir'
8
8
  import { notify } from '@/lib/server/ws-hub'
9
+ import { deleteSession, listSessions, replaceSessions } from '@/lib/server/sessions/session-repository'
10
+ import { stopActiveSessionProcess } from '@/lib/server/runtime/runtime-state'
9
11
  import { getSessionQueueSnapshot, getSessionRunState } from '@/lib/server/runtime/session-run-manager'
10
12
  import { normalizeProviderEndpoint } from '@/lib/openclaw/openclaw-endpoint'
11
13
  import { applyResolvedRoute, resolvePrimaryAgentRoute } from '@/lib/server/agents/agent-runtime-config'
@@ -25,11 +27,11 @@ export async function GET(req: Request) {
25
27
  const endPerf = perf.start('api', 'GET /api/chats')
26
28
  // Note: pruneThreadConnectorMirrors and materializeStreamingAssistantArtifacts
27
29
  // are handled by the daemon periodic health check, not on every list fetch.
28
- const sessions = loadSessions()
30
+ const sessions = listSessions()
29
31
  for (const id of Object.keys(sessions)) {
30
32
  const run = getSessionRunState(id)
31
33
  const queue = getSessionQueueSnapshot(id)
32
- sessions[id].active = active.has(id) || !!run.runningRunId
34
+ sessions[id].active = !!run.runningRunId
33
35
  sessions[id].queuedCount = queue.queueLength
34
36
  sessions[id].currentRunId = run.runningRunId || null
35
37
  }
@@ -59,14 +61,11 @@ export async function DELETE(req: Request) {
59
61
  if (!Array.isArray(ids) || !ids.length) {
60
62
  return new NextResponse('Missing ids', { status: 400 })
61
63
  }
62
- const sessions = loadSessions()
64
+ const sessions = listSessions()
63
65
  let deleted = 0
64
66
  for (const id of ids) {
65
67
  if (!sessions[id]) continue
66
- if (active.has(id)) {
67
- try { active.get(id)?.kill() } catch {}
68
- active.delete(id)
69
- }
68
+ stopActiveSessionProcess(id)
70
69
  deleteSession(id)
71
70
  deleted += 1
72
71
  }
@@ -83,7 +82,7 @@ export async function POST(req: Request) {
83
82
  else if (!cwd) cwd = WORKSPACE_DIR
84
83
 
85
84
  const id = body.id || genId()
86
- const sessions = loadSessions()
85
+ const sessions = listSessions()
87
86
  const agent = body.agentId ? loadAgents()[body.agentId] : null
88
87
  if (isAgentDisabled(agent)) {
89
88
  return NextResponse.json({ error: buildAgentDisabledMessage(agent, 'start chats') }, { status: 409 })
@@ -162,7 +161,7 @@ export async function POST(req: Request) {
162
161
  sessions[id] = (body.provider || body.model || body.credentialId || body.apiEndpoint)
163
162
  ? nextSession
164
163
  : applyResolvedRoute(nextSession, resolvedRoute)
165
- saveSessions(sessions)
164
+ replaceSessions(sessions)
166
165
  notify('sessions')
167
166
  return NextResponse.json(sessions[id])
168
167
  }
@@ -1,6 +1,9 @@
1
1
  import { NextResponse } from 'next/server'
2
2
  import { loadCredentials, deleteCredential } from '@/lib/server/storage'
3
3
  import { notFound } from '@/lib/server/collection-helpers'
4
+ import { log } from '@/lib/server/logger'
5
+
6
+ const TAG = 'api-credentials'
4
7
 
5
8
  export async function DELETE(_req: Request, { params }: { params: Promise<{ id: string }> }) {
6
9
  const { id: credId } = await params
@@ -9,6 +12,6 @@ export async function DELETE(_req: Request, { params }: { params: Promise<{ id:
9
12
  return notFound()
10
13
  }
11
14
  deleteCredential(credId)
12
- console.log(`[credentials] deleted ${credId}`)
15
+ log.info(TAG, `deleted ${credId}`)
13
16
  return new NextResponse('OK')
14
17
  }
@@ -3,6 +3,9 @@ import { inferExtensionPublisherSourceFromUrl } from '@/lib/extension-sources'
3
3
  import { searchClawHub } from '@/lib/server/skills/clawhub-client'
4
4
  import type { ExtensionCatalogSource } from '@/types'
5
5
  import { errorMessage } from '@/lib/shared-utils'
6
+ import { log } from '@/lib/server/logger'
7
+
8
+ const TAG = 'api-extensions-marketplace'
6
9
 
7
10
  export const dynamic = 'force-dynamic'
8
11
 
@@ -71,7 +74,7 @@ export async function GET(req: Request) {
71
74
  })
72
75
  }
73
76
  } catch (err: unknown) {
74
- console.warn('[extensions-marketplace] Registry failed:', {
77
+ log.warn(TAG, 'Registry failed:', {
75
78
  registryUrl: registry.url,
76
79
  error: errorMessage(err),
77
80
  })
@@ -93,7 +96,7 @@ export async function GET(req: Request) {
93
96
  catalogSource: 'clawhub',
94
97
  })))
95
98
  } catch (err: unknown) {
96
- console.warn('[extensions-marketplace] ClawHub failed:', errorMessage(err))
99
+ log.warn(TAG, 'ClawHub failed:', errorMessage(err))
97
100
  }
98
101
 
99
102
  allExtensions.sort((a, b) => {
@@ -1,8 +1,8 @@
1
1
  import { NextResponse } from 'next/server'
2
- import { localIP } from '@/lib/server/storage'
2
+ import { localIP } from '@/lib/server/runtime/network'
3
3
  export const dynamic = 'force-dynamic'
4
4
 
5
5
 
6
- export async function GET(_req: Request) {
6
+ export async function GET() {
7
7
  return NextResponse.json({ ip: localIP(), port: parseInt(process.env.PORT || '3000') })
8
8
  }
@@ -4,6 +4,9 @@ import { getMemoryDb } from '@/lib/server/memory/memory-db'
4
4
  import { loadSettings } from '@/lib/server/storage'
5
5
  import { syncAllSessionArchiveMemories } from '@/lib/server/memory/session-archive-memory'
6
6
  import { DATA_DIR } from '@/lib/server/data-dir'
7
+ import { log } from '@/lib/server/logger'
8
+
9
+ const TAG = 'api-memory-maintenance'
7
10
 
8
11
  function parseBool(value: unknown, fallback: boolean): boolean {
9
12
  if (typeof value === 'boolean') return value
@@ -46,7 +49,7 @@ export async function GET(req: Request) {
46
49
  archiveExportDir: path.join(DATA_DIR, 'session-archives'),
47
50
  })
48
51
  } catch (err: unknown) {
49
- console.error('[memory/maintenance] GET failed:', err)
52
+ log.error(TAG, 'GET failed:', err)
50
53
  return NextResponse.json({ ok: false, error: String((err as Error)?.message || err) }, { status: 500 })
51
54
  }
52
55
  }
@@ -77,7 +80,7 @@ export async function POST(req: Request) {
77
80
  ...result,
78
81
  })
79
82
  } catch (err: unknown) {
80
- console.error('[memory/maintenance] POST failed:', err)
83
+ log.error(TAG, 'POST failed:', err)
81
84
  return NextResponse.json({ ok: false, error: String((err as Error)?.message || err) }, { status: 500 })
82
85
  }
83
86
  }
@@ -3,11 +3,14 @@ import { spawn, type ChildProcess } from 'child_process'
3
3
  import http from 'http'
4
4
  import fs from 'fs'
5
5
  import path from 'path'
6
- import { localIP } from '@/lib/server/storage'
6
+ import { localIP } from '@/lib/server/runtime/network'
7
7
  import { resolveDevServerLaunchDir } from '@/lib/server/runtime/devserver-launch'
8
8
  import { resolvePathWithinBaseDir } from '@/lib/server/path-utils'
9
9
  import { safeParseBody } from '@/lib/server/safe-parse-body'
10
10
  import { hmrSingleton, sleep } from '@/lib/shared-utils'
11
+ import { log } from '@/lib/server/logger'
12
+
13
+ const TAG = 'api-preview-server'
11
14
 
12
15
  // ---------------------------------------------------------------------------
13
16
  // MIME types for static server
@@ -181,7 +184,7 @@ function createStaticServer(dir: string): http.Server {
181
184
  async function startNpmServer(dir: string, command: string[], port: number, framework?: string): Promise<PreviewServer> {
182
185
  // Install deps if node_modules missing
183
186
  if (!fs.existsSync(path.join(dir, 'node_modules'))) {
184
- console.log(`[preview] Installing dependencies in ${dir}`)
187
+ log.info(TAG, `Installing dependencies in ${dir}`)
185
188
  await new Promise<void>((resolve, reject) => {
186
189
  const install = spawn('npm', ['install'], { cwd: dir, stdio: 'pipe' })
187
190
  install.on('close', (code) => code === 0 ? resolve() : reject(new Error(`npm install exited ${code}`)))
@@ -206,14 +209,14 @@ async function startNpmServer(dir: string, command: string[], port: number, fram
206
209
  env,
207
210
  })
208
211
 
209
- let log = ''
212
+ let processOutput = ''
210
213
  let detectedPort = port
211
214
  const urlRe = /https?:\/\/(?:localhost|0\.0\.0\.0|127\.0\.0\.1|[\d.]+):(\d+)/
212
215
 
213
216
  const onData = (chunk: Buffer) => {
214
217
  const text = chunk.toString()
215
- log += text
216
- if (log.length > 10000) log = log.slice(-5000)
218
+ processOutput += text
219
+ if (processOutput.length > 10000) processOutput = processOutput.slice(-5000)
217
220
  const match = text.match(urlRe)
218
221
  if (match) {
219
222
  detectedPort = parseInt(match[1], 10)
@@ -236,7 +239,7 @@ async function startNpmServer(dir: string, command: string[], port: number, fram
236
239
 
237
240
  proc.on('close', () => {
238
241
  servers.delete(dirKey(dir))
239
- console.log(`[preview] npm server stopped for ${dir}`)
242
+ log.info(TAG, `npm server stopped for ${dir}`)
240
243
  })
241
244
  proc.on('error', () => servers.delete(dirKey(dir)))
242
245
 
@@ -246,10 +249,10 @@ async function startNpmServer(dir: string, command: string[], port: number, fram
246
249
  await sleep(5000)
247
250
  if (proc.exitCode !== null) {
248
251
  servers.delete(dirKey(dir))
249
- throw new Error(`npm dev server exited early with code ${proc.exitCode}\n${log.slice(-4000)}`)
252
+ throw new Error(`npm dev server exited early with code ${proc.exitCode}\n${processOutput.slice(-4000)}`)
250
253
  }
251
254
  entry.port = detectedPort
252
- entry.log = log
255
+ entry.log = processOutput
253
256
 
254
257
  return entry
255
258
  }
@@ -295,7 +298,7 @@ export async function POST(req: Request) {
295
298
  const port = await findFreePort()
296
299
 
297
300
  if (project.type === 'npm' && project.devCommand) {
298
- console.log(`[preview] Detected ${project.framework} project in ${launch.launchDir}, running: ${project.devCommand.join(' ')}`)
301
+ log.info(TAG, `Detected ${project.framework} project in ${launch.launchDir}, running: ${project.devCommand.join(' ')}`)
299
302
  try {
300
303
  const entry = await startNpmServer(launch.launchDir, project.devCommand, port, project.framework)
301
304
  return NextResponse.json({
@@ -305,7 +308,7 @@ export async function POST(req: Request) {
305
308
  launchDir: launch.launchDir,
306
309
  })
307
310
  } catch (err: unknown) {
308
- console.error(`[preview] npm server failed, falling back to static:`, err)
311
+ log.error(TAG, 'npm server failed, falling back to static:', err)
309
312
  // Fall through to static server
310
313
  }
311
314
  }
@@ -319,7 +322,7 @@ export async function POST(req: Request) {
319
322
 
320
323
  const entry: PreviewServer = { type: 'static', server, port, dir, startedAt: Date.now(), log: '' }
321
324
  servers.set(key, entry)
322
- console.log(`[preview] Started static server for ${dir} on port ${port}`)
325
+ log.info(TAG, `Started static server for ${dir} on port ${port}`)
323
326
 
324
327
  return NextResponse.json(buildResponse(entry))
325
328
 
@@ -332,7 +335,7 @@ export async function POST(req: Request) {
332
335
  }
333
336
  if (srv.server) srv.server.close()
334
337
  servers.delete(key)
335
- console.log(`[preview] Stopped server for ${launch.launchDir}`)
338
+ log.info(TAG, `Stopped server for ${launch.launchDir}`)
336
339
  }
337
340
  return NextResponse.json({ running: false, dir: launch.launchDir })
338
341
 
@@ -1,65 +1,26 @@
1
1
  import { NextResponse } from 'next/server'
2
- import { loadProjects, saveProjects, deleteProject, loadAgents, saveAgents, loadTasks, saveTasks, loadSchedules, saveSchedules, loadSkills, saveSkills, loadSecrets, saveSecrets } from '@/lib/server/storage'
3
- import { mutateItem, deleteItem, notFound, type CollectionOps } from '@/lib/server/collection-helpers'
4
- import { ensureProjectWorkspace, normalizeProjectPatchInput } from '@/lib/server/project-utils'
5
- import { notify } from '@/lib/server/ws-hub'
2
+ import { notFound } from '@/lib/server/collection-helpers'
3
+ import { deleteProjectAndDetachReferences, getProject, updateProject } from '@/lib/server/projects/project-service'
6
4
  import { safeParseBody } from '@/lib/server/safe-parse-body'
7
5
 
8
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
9
- const ops: CollectionOps<any> = { load: loadProjects, save: saveProjects, deleteFn: deleteProject, topic: 'projects' }
10
-
11
6
  export async function GET(_req: Request, { params }: { params: Promise<{ id: string }> }) {
12
7
  const { id } = await params
13
- const projects = loadProjects()
14
- if (!projects[id]) return notFound()
15
- return NextResponse.json(projects[id])
8
+ const project = getProject(id)
9
+ if (!project) return notFound()
10
+ return NextResponse.json(project)
16
11
  }
17
12
 
18
13
  export async function PUT(req: Request, { params }: { params: Promise<{ id: string }> }) {
19
14
  const { id } = await params
20
15
  const { data: body, error } = await safeParseBody<Record<string, unknown>>(req)
21
16
  if (error) return error
22
- const result = mutateItem(ops, id, (project) => {
23
- const patch = normalizeProjectPatchInput(body && typeof body === 'object' ? body as Record<string, unknown> : {})
24
- Object.assign(project, patch, { updatedAt: Date.now() })
25
- delete (project as Record<string, unknown>).id
26
- project.id = id
27
- return project
28
- })
17
+ const result = updateProject(id, body && typeof body === 'object' ? body : {})
29
18
  if (!result) return notFound()
30
- ensureProjectWorkspace(id, result.name)
31
19
  return NextResponse.json(result)
32
20
  }
33
21
 
34
22
  export async function DELETE(_req: Request, { params }: { params: Promise<{ id: string }> }) {
35
23
  const { id } = await params
36
- if (!deleteItem(ops, id)) return notFound()
37
-
38
- // Clear projectId from referencing entities
39
- const clearProjectId = (load: () => Record<string, Record<string, unknown>>, save: (d: Record<string, Record<string, unknown>>) => void, topic: string) => {
40
- const items = load()
41
- let changed = false
42
- for (const item of Object.values(items)) {
43
- if (item.projectId === id) {
44
- item.projectId = undefined
45
- changed = true
46
- }
47
- }
48
- if (changed) {
49
- save(items)
50
- notify(topic)
51
- }
52
- }
53
-
54
- clearProjectId(
55
- loadAgents as unknown as () => Record<string, Record<string, unknown>>,
56
- saveAgents as unknown as (data: Record<string, Record<string, unknown>>) => void,
57
- 'agents',
58
- )
59
- clearProjectId(loadTasks as unknown as () => Record<string, Record<string, unknown>>, saveTasks as unknown as (data: Record<string, Record<string, unknown>>) => void, 'tasks')
60
- clearProjectId(loadSchedules as unknown as () => Record<string, Record<string, unknown>>, saveSchedules as unknown as (data: Record<string, Record<string, unknown>>) => void, 'schedules')
61
- clearProjectId(loadSkills as unknown as () => Record<string, Record<string, unknown>>, saveSkills as unknown as (data: Record<string, Record<string, unknown>>) => void, 'skills')
62
- clearProjectId(loadSecrets as unknown as () => Record<string, Record<string, unknown>>, saveSecrets as unknown as (data: Record<string, Record<string, unknown>>) => void, 'secrets')
63
-
24
+ if (!deleteProjectAndDetachReferences(id)) return notFound()
64
25
  return NextResponse.json({ ok: true })
65
26
  }