@swarmclawai/swarmclaw 1.4.0 → 1.4.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 (41) hide show
  1. package/README.md +6 -73
  2. package/next.config.ts +9 -4
  3. package/package.json +10 -8
  4. package/scripts/build-bootstrap-env.mjs +24 -0
  5. package/scripts/run-next-build.mjs +74 -0
  6. package/scripts/run-next-typegen.mjs +61 -0
  7. package/src/app/api/approvals/route.test.ts +29 -3
  8. package/src/app/api/approvals/route.ts +13 -7
  9. package/src/app/api/chats/[id]/chat/route.test.ts +64 -0
  10. package/src/app/api/chats/[id]/chat/route.ts +24 -8
  11. package/src/app/api/chats/chat-route.test.ts +68 -0
  12. package/src/app/api/connectors/[id]/doctor/route.test.ts +97 -0
  13. package/src/app/api/connectors/[id]/doctor/route.ts +26 -1
  14. package/src/app/api/connectors/connector-doctor-route.test.ts +1 -0
  15. package/src/app/api/logs/route.test.ts +61 -0
  16. package/src/app/api/logs/route.ts +35 -0
  17. package/src/app/api/tts/route.test.ts +82 -0
  18. package/src/app/api/tts/route.ts +13 -6
  19. package/src/app/api/tts/stream/route.ts +12 -5
  20. package/src/app/error.tsx +32 -0
  21. package/src/app/global-error.tsx +33 -0
  22. package/src/cli/index.js +3 -0
  23. package/src/cli/spec.js +1 -0
  24. package/src/components/layout/error-boundary.tsx +12 -30
  25. package/src/components/layout/error-fallback.tsx +61 -0
  26. package/src/features/swarmfeed/queries.ts +3 -3
  27. package/src/lib/app/report-client-error.ts +52 -0
  28. package/src/lib/providers/anthropic.ts +9 -1
  29. package/src/lib/providers/ollama.ts +34 -14
  30. package/src/lib/providers/openai.ts +9 -1
  31. package/src/lib/providers/openclaw.ts +3 -3
  32. package/src/lib/server/chat-execution/chat-turn-preparation.ts +19 -12
  33. package/src/lib/server/connectors/swarmdock.ts +1 -1
  34. package/src/lib/server/messages/message-repository.ts +31 -0
  35. package/src/lib/server/provider-health.ts +19 -3
  36. package/src/lib/server/safe-parse-body.test.ts +32 -0
  37. package/src/lib/server/safe-parse-body.ts +20 -3
  38. package/src/lib/server/storage.ts +13 -4
  39. package/src/lib/swarmfeed-client.ts +1 -1
  40. package/tsconfig.json +1 -2
  41. package/src/.env.local +0 -4
@@ -0,0 +1,52 @@
1
+ 'use client'
2
+
3
+ type ReportClientErrorInput = {
4
+ source: string
5
+ error: Error | string | unknown
6
+ componentStack?: string | null
7
+ digest?: string | null
8
+ }
9
+
10
+ const reportedClientErrors = new Set<string>()
11
+
12
+ function truncate(value: string | null | undefined, max: number): string | undefined {
13
+ if (!value) return undefined
14
+ return value.length > max ? value.slice(0, max) : value
15
+ }
16
+
17
+ export function reportClientError(input: ReportClientErrorInput) {
18
+ if (typeof window === 'undefined') return
19
+
20
+ const message = input.error instanceof Error
21
+ ? input.error.message
22
+ : typeof input.error === 'string'
23
+ ? input.error
24
+ : String(input.error)
25
+
26
+ const stack = input.error instanceof Error ? input.error.stack : undefined
27
+ const fingerprint = [
28
+ input.source,
29
+ message,
30
+ input.digest || '',
31
+ input.componentStack || '',
32
+ ].join('|')
33
+
34
+ if (reportedClientErrors.has(fingerprint)) return
35
+ reportedClientErrors.add(fingerprint)
36
+
37
+ void fetch('/api/logs', {
38
+ method: 'POST',
39
+ headers: { 'content-type': 'application/json' },
40
+ body: JSON.stringify({
41
+ source: input.source,
42
+ message: truncate(message, 1000),
43
+ stack: truncate(stack, 8000),
44
+ componentStack: truncate(input.componentStack, 8000),
45
+ digest: truncate(input.digest, 200),
46
+ url: truncate(window.location.href, 2000),
47
+ pathname: truncate(window.location.pathname, 1000),
48
+ userAgent: truncate(window.navigator.userAgent, 1000),
49
+ }),
50
+ keepalive: true,
51
+ }).catch(() => {})
52
+ }
@@ -90,6 +90,7 @@ export function streamAnthropicChat({ session, message, imagePath, apiKey, syste
90
90
  }
91
91
 
92
92
  let buf = ''
93
+ let malformedChunkLogged = false
93
94
  apiRes.on('data', (chunk: Buffer) => {
94
95
  if (abortController.aborted) return
95
96
  buf += chunk.toString()
@@ -112,7 +113,14 @@ export function streamAnthropicChat({ session, message, imagePath, apiKey, syste
112
113
  if (parsed.type === 'message_delta' && parsed.usage) {
113
114
  usageOutput = parsed.usage.output_tokens || 0
114
115
  }
115
- } catch {}
116
+ } catch {
117
+ if (!malformedChunkLogged) {
118
+ malformedChunkLogged = true
119
+ log.warn(TAG, `[${session.id}] failed to parse Anthropic stream chunk`, {
120
+ sample: data.slice(0, 200),
121
+ })
122
+ }
123
+ }
116
124
  }
117
125
  })
118
126
 
@@ -2,6 +2,7 @@ import fs from 'fs'
2
2
  import http from 'http'
3
3
  import https from 'https'
4
4
  import type { StreamChatOptions } from './index'
5
+ import { streamOpenAiChat } from './openai'
5
6
  import { IMAGE_EXTS, TEXT_EXTS, MAX_HISTORY_MESSAGES, writeSSE } from './provider-defaults'
6
7
  import { log } from '@/lib/server/logger'
7
8
  import { resolveOllamaRuntimeConfig } from '@/lib/server/ollama-runtime'
@@ -9,23 +10,34 @@ import { resolveImagePath } from '@/lib/server/resolve-image'
9
10
 
10
11
  const TAG = 'provider-ollama'
11
12
 
12
- export function streamOllamaChat({ session, message, imagePath, apiKey, write, active, loadHistory, onUsage, signal }: StreamChatOptions): Promise<string> {
13
+ /** Ollama Cloud uses the OpenAI-compatible /v1 endpoint, not the native /api/chat protocol. */
14
+ const OLLAMA_CLOUD_OPENAI_ENDPOINT = 'https://ollama.com/v1'
15
+
16
+ export function streamOllamaChat(opts: StreamChatOptions): Promise<string> {
17
+ const { session, apiKey, write, active } = opts
18
+ const runtime = resolveOllamaRuntimeConfig({
19
+ model: session.model,
20
+ ollamaMode: session.ollamaMode,
21
+ apiKey,
22
+ apiEndpoint: session.apiEndpoint,
23
+ })
24
+
25
+ if (runtime.useCloud) {
26
+ if (!runtime.apiKey) {
27
+ writeSSE(write, 'err', 'Ollama Cloud model requires an API key. Set OLLAMA_API_KEY or attach an Ollama credential.')
28
+ active.delete(session.id)
29
+ return Promise.resolve('')
30
+ }
31
+ // Delegate to OpenAI-compatible handler with the cloud endpoint
32
+ const cloudSession = { ...session, model: runtime.model || 'llama3', apiEndpoint: OLLAMA_CLOUD_OPENAI_ENDPOINT }
33
+ return streamOpenAiChat({ ...opts, session: cloudSession, apiKey: runtime.apiKey })
34
+ }
35
+
36
+ const { message, imagePath, loadHistory, onUsage, signal } = opts
13
37
  return new Promise((resolve, reject) => {
14
38
  const messages = buildMessages(session, message, imagePath, loadHistory)
15
- const runtime = resolveOllamaRuntimeConfig({
16
- model: session.model,
17
- ollamaMode: session.ollamaMode,
18
- apiKey,
19
- apiEndpoint: session.apiEndpoint,
20
- })
21
39
  const model = runtime.model || 'llama3'
22
40
  const endpoint = runtime.endpoint
23
- if (runtime.useCloud && !runtime.apiKey) {
24
- writeSSE(write, 'err', 'Ollama Cloud model requires an API key. Set OLLAMA_API_KEY or attach an Ollama credential.')
25
- active.delete(session.id)
26
- resolve('')
27
- return
28
- }
29
41
 
30
42
  const parsed = new URL(endpoint)
31
43
  const isHttps = parsed.protocol === 'https:'
@@ -81,6 +93,7 @@ export function streamOllamaChat({ session, message, imagePath, apiKey, write, a
81
93
  }
82
94
 
83
95
  let buf = ''
96
+ let malformedChunkLogged = false
84
97
  apiRes.on('data', (chunk: Buffer) => {
85
98
  if (abortController.aborted) return
86
99
  buf += chunk.toString()
@@ -103,7 +116,14 @@ export function streamOllamaChat({ session, message, imagePath, apiKey, write, a
103
116
  onUsage({ inputTokens: input, outputTokens: output })
104
117
  }
105
118
  }
106
- } catch {}
119
+ } catch {
120
+ if (!malformedChunkLogged) {
121
+ malformedChunkLogged = true
122
+ log.warn(TAG, `[${session.id}] failed to parse Ollama stream chunk`, {
123
+ sample: line.slice(0, 200),
124
+ })
125
+ }
126
+ }
107
127
  }
108
128
  })
109
129
 
@@ -147,6 +147,7 @@ export function streamOpenAiChat({ session, message, imagePath, imageUrl, apiKey
147
147
  const reader = res.body.getReader()
148
148
  const decoder = new TextDecoder()
149
149
  let buf = ''
150
+ let malformedChunkLogged = false
150
151
 
151
152
  while (true) {
152
153
  const { done, value } = await reader.read()
@@ -175,7 +176,14 @@ export function streamOpenAiChat({ session, message, imagePath, imageUrl, apiKey
175
176
  outputTokens: parsed.usage.completion_tokens || 0,
176
177
  })
177
178
  }
178
- } catch {}
179
+ } catch {
180
+ if (!malformedChunkLogged) {
181
+ malformedChunkLogged = true
182
+ log.warn(TAG, `[${session.id}] failed to parse OpenAI stream chunk`, {
183
+ sample: data.slice(0, 200),
184
+ })
185
+ }
186
+ }
179
187
  }
180
188
  }
181
189
 
@@ -9,6 +9,7 @@ import { deriveOpenClawWsUrl } from '@/lib/openclaw/openclaw-endpoint'
9
9
  import { normalizeOpenClawAgentId } from '@/lib/openclaw/openclaw-agent-id'
10
10
  import { loadAgents } from '../server/storage'
11
11
  import { getSharedDeviceToken } from '../server/openclaw/sync'
12
+ import { DATA_DIR } from '../server/data-dir'
12
13
  import {
13
14
  resolveOpenClawGatewayAgentIdFromList,
14
15
  type OpenClawGatewayAgentSummary,
@@ -54,9 +55,8 @@ function resolveCliStateDir(): string {
54
55
  }
55
56
 
56
57
  function getSwarmClawIdentityPath(): string {
57
- const dataDir = path.join(process.cwd(), 'data')
58
- if (!fs.existsSync(dataDir)) fs.mkdirSync(dataDir, { recursive: true })
59
- return path.join(dataDir, 'openclaw-device.json')
58
+ if (!fs.existsSync(DATA_DIR)) fs.mkdirSync(DATA_DIR, { recursive: true })
59
+ return path.join(DATA_DIR, 'openclaw-device.json')
60
60
  }
61
61
 
62
62
  function tryLoadIdentityFile(filePath: string): DeviceIdentity | null {
@@ -513,7 +513,7 @@ export async function prepareChatTurn(input: ExecuteChatTurnInput): Promise<Prep
513
513
  const session = getSession(sessionId)
514
514
  if (!session) throw new Error(`Session not found: ${sessionId}`)
515
515
  const runStartedAt = Date.now()
516
- const runMessageStartIndex = getMessageCount(sessionId)
516
+ let runMessageStartIndex = getMessageCount(sessionId)
517
517
 
518
518
  const appSettings = loadSettings()
519
519
  const lifecycleRunId = runId || `${sessionId}:${runStartedAt}`
@@ -725,17 +725,10 @@ export async function prepareChatTurn(input: ExecuteChatTurnInput): Promise<Prep
725
725
  }
726
726
  }
727
727
 
728
- const providerType = sessionForRun.provider || 'claude-cli'
729
- const provider = getProvider(providerType)
730
- if (!provider) throw new Error(`Unknown provider: ${providerType}`)
731
-
732
- if (providerType === 'claude-cli' && !fs.existsSync(session.cwd)) {
733
- throw new Error(`Directory not found: ${session.cwd}`)
734
- }
735
-
736
- const apiKey = resolveApiKeyForSession(sessionForRun, provider)
737
- const hideAssistantTranscript = internal && source === 'main-loop-followup'
738
-
728
+ // Persist the user message BEFORE provider/credential resolution so that if
729
+ // provider resolution throws (unknown provider, missing credentials, etc.),
730
+ // the user message is already in the DB and won't disappear from the chat
731
+ // when the frontend's refreshMessages overwrites the optimistic local copy.
739
732
  const shouldPersistUserMessage = shouldPersistInboundUserMessage(internal, source)
740
733
  if (shouldPersistUserMessage) {
741
734
  const [linkAnalysis, semantics] = await Promise.all([
@@ -805,8 +798,22 @@ export async function prepareChatTurn(input: ExecuteChatTurnInput): Promise<Prep
805
798
  }
806
799
  }
807
800
  }
801
+ // Update runMessageStartIndex to account for newly appended user message(s)
802
+ // so that partial persistence and finalization don't overwrite them.
803
+ runMessageStartIndex = getMessageCount(sessionId)
808
804
  }
809
805
 
806
+ const providerType = sessionForRun.provider || 'claude-cli'
807
+ const provider = getProvider(providerType)
808
+ if (!provider) throw new Error(`Unknown provider: ${providerType}`)
809
+
810
+ if (providerType === 'claude-cli' && !fs.existsSync(session.cwd)) {
811
+ throw new Error(`Directory not found: ${session.cwd}`)
812
+ }
813
+
814
+ const apiKey = resolveApiKeyForSession(sessionForRun, provider)
815
+ const hideAssistantTranscript = internal && source === 'main-loop-followup'
816
+
810
817
  const useLocalOpenClawNativeRuntime = providerType === 'openclaw' && isLocalOpenClawEndpoint(sessionForRun.apiEndpoint)
811
818
  const enabledSessionExtensions = getEnabledCapabilityIds(sessionForRun)
812
819
  const hasExtensions = enabledSessionExtensions.length > 0
@@ -22,7 +22,7 @@ interface SwarmDockConfig {
22
22
  function parseConfig(connector: Connector): SwarmDockConfig {
23
23
  const c = connector.config || {}
24
24
  return {
25
- apiUrl: c.apiUrl || 'https://api.swarmdock.ai',
25
+ apiUrl: c.apiUrl || 'https://swarmdock-api.onrender.com',
26
26
  walletAddress: c.walletAddress || '',
27
27
  agentDescription: c.agentDescription || connector.name || '',
28
28
  skills: c.skills || '',
@@ -286,6 +286,37 @@ export function clearMessages(sessionId: string): void {
286
286
  /** Replace the entire message list (used after in-memory prune operations). */
287
287
  export function replaceAllMessages(sessionId: string, messages: Message[]): void {
288
288
  perf.measureSync('message-repo', 'replaceAllMessages', () => {
289
+ // Safety guard: reload current user messages from DB and ensure none are
290
+ // dropped by the replacement. This prevents races where partial persistence
291
+ // or finalization load a stale snapshot that's missing recently-appended
292
+ // user messages.
293
+ const currentRows = stmts().selectAll.all(sessionId) as Array<{ data: string }>
294
+ const currentUserMessages: Message[] = []
295
+ for (const row of currentRows) {
296
+ const m = parseMsg(row.data)
297
+ if (m && m.role === 'user') currentUserMessages.push(m)
298
+ }
299
+ const replacementUserTimes = new Set(
300
+ messages.filter(m => m.role === 'user' && typeof m.time === 'number').map(m => m.time),
301
+ )
302
+ const missingUsers = currentUserMessages.filter(
303
+ m => typeof m.time === 'number' && !replacementUserTimes.has(m.time),
304
+ )
305
+ if (missingUsers.length > 0) {
306
+ // Re-insert missing user messages at their correct position (before the
307
+ // first assistant message that follows them chronologically).
308
+ for (const user of missingUsers) {
309
+ let insertIdx = messages.length
310
+ for (let i = 0; i < messages.length; i++) {
311
+ if (messages[i].role === 'assistant' && typeof messages[i].time === 'number'
312
+ && (messages[i].time as number) >= (user.time as number)) {
313
+ insertIdx = i
314
+ break
315
+ }
316
+ }
317
+ messages.splice(insertIdx, 0, user)
318
+ }
319
+ }
289
320
  withTransaction(() => {
290
321
  stmts().deleteAll.run(sessionId)
291
322
  const ins = stmts().insert
@@ -1,6 +1,9 @@
1
1
  import { spawnSync } from 'child_process'
2
2
  import { errorMessage, hmrSingleton, jitteredBackoff } from '@/lib/shared-utils'
3
3
  import { upsertStoredItem, loadCollection } from './storage'
4
+ import { log } from './logger'
5
+
6
+ const TAG = 'provider-health'
4
7
 
5
8
  type DelegateTool = 'delegate_to_claude_code' | 'delegate_to_codex_cli' | 'delegate_to_opencode_cli' | 'delegate_to_gemini_cli'
6
9
 
@@ -72,7 +75,12 @@ export function markProviderFailure(providerId: string, error: string, credentia
72
75
  })
73
76
  try {
74
77
  upsertStoredItem('provider_health', key, states.get(key)!)
75
- } catch {}
78
+ } catch (err) {
79
+ log.warn(TAG, 'Failed to persist provider failure state', {
80
+ providerKey: key,
81
+ error: errorMessage(err),
82
+ })
83
+ }
76
84
  }
77
85
 
78
86
  export function markProviderSuccess(providerId: string, credentialId?: string | null): void {
@@ -88,7 +96,12 @@ export function markProviderSuccess(providerId: string, credentialId?: string |
88
96
  })
89
97
  try {
90
98
  upsertStoredItem('provider_health', key, states.get(key)!)
91
- } catch {}
99
+ } catch (err) {
100
+ log.warn(TAG, 'Failed to persist provider success state', {
101
+ providerKey: key,
102
+ error: errorMessage(err),
103
+ })
104
+ }
92
105
  }
93
106
 
94
107
  export function isProviderCoolingDown(providerId: string, credentialId?: string | null): boolean {
@@ -195,7 +208,10 @@ export function restoreProviderHealthState(): number {
195
208
  }
196
209
  }
197
210
  return restored
198
- } catch { return 0 }
211
+ } catch (err) {
212
+ log.warn(TAG, 'Failed to restore persisted provider health state', { error: errorMessage(err) })
213
+ return 0
214
+ }
199
215
  }
200
216
 
201
217
  // ---------------------------------------------------------------------------
@@ -1,5 +1,6 @@
1
1
  import assert from 'node:assert/strict'
2
2
  import { describe, it, before, after } from 'node:test'
3
+ import { z } from 'zod'
3
4
 
4
5
  let safeParseBody: typeof import('@/lib/server/safe-parse-body').safeParseBody
5
6
  before(async () => {
@@ -50,4 +51,35 @@ describe('safeParseBody', () => {
50
51
  assert.equal(result.data!.name, 'test')
51
52
  assert.equal(result.data!.count, 7)
52
53
  })
54
+
55
+ it('validates the parsed body against a provided zod schema', async () => {
56
+ const result = await safeParseBody(
57
+ jsonRequest(JSON.stringify({ name: 'ok', count: 3 })),
58
+ z.object({
59
+ name: z.string().min(1),
60
+ count: z.number().int().nonnegative(),
61
+ }),
62
+ )
63
+ assert.equal(result.error, undefined)
64
+ assert.deepEqual(result.data, { name: 'ok', count: 3 })
65
+ })
66
+
67
+ it('returns a 400 validation error when schema parsing fails', async () => {
68
+ const result = await safeParseBody(
69
+ jsonRequest(JSON.stringify({ name: '', count: -1 })),
70
+ z.object({
71
+ name: z.string().min(1, 'name is required'),
72
+ count: z.number().int().nonnegative('count must be non-negative'),
73
+ }),
74
+ )
75
+ assert.equal(result.data, undefined)
76
+ assert.ok(result.error)
77
+ assert.equal(result.error.status, 400)
78
+ const body = await result.error.json()
79
+ assert.equal(body.error, 'Validation failed')
80
+ assert.deepEqual(body.issues, [
81
+ { path: 'name', message: 'name is required' },
82
+ { path: 'count', message: 'count must be non-negative' },
83
+ ])
84
+ })
53
85
  })
@@ -1,4 +1,7 @@
1
1
  import { NextResponse } from 'next/server'
2
+ import { z } from 'zod'
3
+
4
+ import { formatZodError } from '@/lib/validation/schemas'
2
5
 
3
6
  type SafeResult<T> = { data: T; error?: never } | { data?: never; error: NextResponse }
4
7
 
@@ -6,11 +9,25 @@ type SafeResult<T> = { data: T; error?: never } | { data?: never; error: NextRes
6
9
  * Wraps `req.json()` so malformed/empty bodies return a 400
7
10
  * instead of throwing an unhandled error (500).
8
11
  */
9
- export async function safeParseBody<T = Record<string, unknown>>(req: Request): Promise<SafeResult<T>> {
12
+ export async function safeParseBody<T = Record<string, unknown>>(
13
+ req: Request,
14
+ schema?: z.ZodType<T>,
15
+ ): Promise<SafeResult<T>> {
16
+ let raw: unknown
10
17
  try {
11
- const data = (await req.json()) as T
12
- return { data }
18
+ raw = await req.json()
13
19
  } catch {
14
20
  return { error: NextResponse.json({ error: 'Invalid or missing request body' }, { status: 400 }) }
15
21
  }
22
+
23
+ if (!schema) {
24
+ return { data: raw as T }
25
+ }
26
+
27
+ const parsed = schema.safeParse(raw)
28
+ if (!parsed.success) {
29
+ return { error: NextResponse.json(formatZodError(parsed.error), { status: 400 }) }
30
+ }
31
+
32
+ return { data: parsed.data }
16
33
  }
@@ -6,12 +6,13 @@ import Database from 'better-sqlite3'
6
6
  import { perf } from '@/lib/server/runtime/perf'
7
7
  import { log } from '@/lib/server/logger'
8
8
  import { notify } from '@/lib/server/ws-hub'
9
-
10
- const TAG = 'storage'
11
9
  import { DATA_DIR, IS_BUILD_BOOTSTRAP, WORKSPACE_DIR } from './data-dir'
12
10
  import { normalizeHeartbeatSettingFields } from '@/lib/runtime/heartbeat-defaults'
13
11
  import { normalizeRuntimeSettingFields } from '@/lib/runtime/runtime-loop'
14
12
  import { normalizeCapabilitySelection } from '@/lib/capability-selection'
13
+
14
+ const TAG = 'storage'
15
+ const malformedRecordWarnings = new Set<string>()
15
16
  import type {
16
17
  Agent,
17
18
  AppNotification,
@@ -236,8 +237,16 @@ function loadCollectionWithNormalizationState(table: string): {
236
237
  if (!normalized || typeof normalized !== 'object' || Array.isArray(normalized)) continue
237
238
  result[id] = normalized as StoredObject
238
239
  if (changed) normalizedCount += 1
239
- } catch {
240
- // Ignore malformed records instead of crashing list endpoints.
240
+ } catch (err) {
241
+ const fingerprint = `${table}:${id}`
242
+ if (!malformedRecordWarnings.has(fingerprint)) {
243
+ malformedRecordWarnings.add(fingerprint)
244
+ log.warn(TAG, 'Ignoring malformed stored record during collection load', {
245
+ table,
246
+ id,
247
+ error: err instanceof Error ? err.message : String(err),
248
+ })
249
+ }
241
250
  }
242
251
  }
243
252
  endPerf({ count: raw.size, normalizedCount })
@@ -6,7 +6,7 @@ interface SwarmFeedConfig {
6
6
  }
7
7
 
8
8
  const config = hmrSingleton<SwarmFeedConfig>('swarmfeed_config', () => ({
9
- apiUrl: process.env.SWARMFEED_API_URL || 'http://localhost:3700',
9
+ apiUrl: process.env.SWARMFEED_API_URL || 'https://swarmfeed-api.onrender.com',
10
10
  }))
11
11
 
12
12
  /**
package/tsconfig.json CHANGED
@@ -33,9 +33,8 @@
33
33
  "**/*.ts",
34
34
  "**/*.tsx",
35
35
  ".next/types/**/*.ts",
36
- ".next/dev/types/**/*.ts",
37
36
  "**/*.mts",
38
- ".next/dev/dev/types/**/*.ts"
37
+ ".next/dev/types/**/*.ts"
39
38
  ],
40
39
  "exclude": [
41
40
  "node_modules",
package/src/.env.local DELETED
@@ -1,4 +0,0 @@
1
-
2
- CREDENTIAL_SECRET=903abc19d0ffc461795f39d90f55b68cdc2c35e930f1115374f11a4ff7ddd293
3
-
4
- ACCESS_KEY=9cf202df60e837ff327ae4e16b257d1f