@swarmclawai/swarmclaw 1.9.26 → 1.9.27

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.
package/README.md CHANGED
@@ -151,14 +151,14 @@ clawhub install swarmclaw
151
151
 
152
152
  [Browse on ClawHub](https://clawhub.ai/skills/swarmclaw)
153
153
 
154
- ## v1.9.26 Highlights
154
+ ## v1.9.27 Highlights
155
155
 
156
- Output hygiene follow-up: empty successful LLM turns now stay silent instead of being rewritten as user-visible errors.
156
+ Desktop compatibility and provider-save repair for Intel Mac users and OpenRouter setup.
157
157
 
158
- - **Silent empty completions.** Blank successful runs no longer become `Error: Run completed...` assistant messages.
159
- - **Connector-safe final text.** Slack and other connectors no longer receive synthetic error text for intentional silence or quiet no-op turns.
160
- - **Real errors preserved.** Explicit provider failures and streamed provider errors still surface as terminal errors.
161
- - **Regression coverage.** Chat-execution tests now lock the distinction between empty success and real failure.
158
+ - **Intel macOS native modules.** The desktop packaging hook now rebuilds Electron-loaded native modules with the target architecture and blocks a release if an x64 macOS bundle contains an arm64-only required addon.
159
+ - **OpenRouter save repair.** Provider updates now tolerate UI metadata fields like `id`, `type`, `createdAt`, and `updatedAt` without persisting them, while still rejecting unrelated unknown fields.
160
+ - **Downloads clarity.** The downloads page no longer guesses Apple Silicon when a browser hides the Mac architecture, so Intel users can choose the x64 DMG explicitly.
161
+ - **Regression coverage.** Provider route and Electron after-pack tests cover the reported failure modes.
162
162
 
163
163
  ## Hosted Deploys
164
164
 
@@ -410,6 +410,15 @@ Operational docs: https://swarmclaw.ai/docs/observability
410
410
 
411
411
  ## Releases
412
412
 
413
+ ### v1.9.27 Highlights
414
+
415
+ Desktop compatibility and provider-save repair for Intel Mac users and OpenRouter setup.
416
+
417
+ - **Intel macOS native modules.** The desktop packaging hook now rebuilds Electron-loaded native modules with the target architecture and blocks a release if an x64 macOS bundle contains an arm64-only required addon.
418
+ - **OpenRouter save repair.** Provider updates now tolerate UI metadata fields like `id`, `type`, `createdAt`, and `updatedAt` without persisting them, while still rejecting unrelated unknown fields.
419
+ - **Downloads clarity.** The downloads page no longer guesses Apple Silicon when a browser hides the Mac architecture, so Intel users can choose the x64 DMG explicitly.
420
+ - **Regression coverage.** Provider route and Electron after-pack tests cover the reported failure modes.
421
+
413
422
  ### v1.9.26 Highlights
414
423
 
415
424
  Output hygiene follow-up: empty successful LLM turns now stay silent instead of being rewritten as user-visible errors.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@swarmclawai/swarmclaw",
3
- "version": "1.9.26",
3
+ "version": "1.9.27",
4
4
  "description": "Build and run autonomous AI agents with OpenClaw, Hermes, multiple model providers, orchestration, delegation, memory, skills, schedules, and chat connectors.",
5
5
  "main": "electron-dist/main.js",
6
6
  "license": "MIT",
@@ -48,7 +48,52 @@ test('provider route upserts builtin override records for enablement changes', (
48
48
  assert.equal(output.responsePayload.isEnabled, false)
49
49
  })
50
50
 
51
- test('provider route rejects unknown fields per ProviderUpdateSchema.strict()', () => {
51
+ test('provider route ignores frontend metadata fields without persisting them', () => {
52
+ const output = runWithTempDataDir<{
53
+ status: number
54
+ providerConfig: {
55
+ id: string
56
+ type: string
57
+ name: string
58
+ isEnabled: boolean
59
+ updatedAt: number
60
+ }
61
+ }>(`
62
+ const storageMod = await import('./src/lib/server/storage')
63
+ const routeMod = await import('./src/app/api/providers/[id]/route')
64
+ const storage = storageMod.default || storageMod
65
+ const route = routeMod.default || routeMod
66
+
67
+ const response = await route.PUT(
68
+ new Request('http://local/api/providers/openai', {
69
+ method: 'PUT',
70
+ headers: { 'content-type': 'application/json' },
71
+ body: JSON.stringify({
72
+ id: 'wrong-id',
73
+ type: 'custom',
74
+ createdAt: '123',
75
+ updatedAt: '456',
76
+ isEnabled: false,
77
+ }),
78
+ }),
79
+ { params: Promise.resolve({ id: 'openai' }) },
80
+ )
81
+
82
+ console.log(JSON.stringify({
83
+ status: response.status,
84
+ providerConfig: storage.loadProviderConfigs().openai,
85
+ }))
86
+ `, { prefix: 'swarmclaw-provider-route-strict-test-' })
87
+
88
+ assert.equal(output.status, 200)
89
+ assert.equal(output.providerConfig.id, 'openai')
90
+ assert.equal(output.providerConfig.type, 'builtin')
91
+ assert.equal(output.providerConfig.name, 'OpenAI')
92
+ assert.equal(output.providerConfig.isEnabled, false)
93
+ assert.equal(typeof output.providerConfig.updatedAt, 'number')
94
+ })
95
+
96
+ test('provider route still rejects unknown non-metadata fields', () => {
52
97
  const output = runWithTempDataDir<{ status: number }>(`
53
98
  const routeMod = await import('./src/app/api/providers/[id]/route')
54
99
  const route = routeMod.default || routeMod
@@ -57,13 +102,13 @@ test('provider route rejects unknown fields per ProviderUpdateSchema.strict()',
57
102
  new Request('http://local/api/providers/openai', {
58
103
  method: 'PUT',
59
104
  headers: { 'content-type': 'application/json' },
60
- body: JSON.stringify({ type: 'builtin', isEnabled: true }),
105
+ body: JSON.stringify({ unexpectedField: true, isEnabled: true }),
61
106
  }),
62
107
  { params: Promise.resolve({ id: 'openai' }) },
63
108
  )
64
109
 
65
110
  console.log(JSON.stringify({ status: response.status }))
66
- `, { prefix: 'swarmclaw-provider-route-strict-test-' })
111
+ `, { prefix: 'swarmclaw-provider-route-unknown-field-test-' })
67
112
 
68
113
  assert.equal(output.status, 400)
69
114
  })
@@ -8,6 +8,7 @@ import { ProviderUpdateSchema, formatZodError } from '@/lib/validation/schemas'
8
8
 
9
9
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
10
10
  const ops: CollectionOps<any> = { load: loadProviderConfigs, save: saveProviderConfigs, topic: 'providers' }
11
+ const PROVIDER_UPDATE_WRITABLE_KEYS = new Set(['name', 'baseUrl', 'models', 'credentialId', 'isEnabled', 'requiresApiKey', 'notes'])
11
12
 
12
13
  export async function GET(_req: Request, { params }: { params: Promise<{ id: string }> }) {
13
14
  const { id } = await params
@@ -27,7 +28,7 @@ export async function PUT(req: Request, { params }: { params: Promise<{ id: stri
27
28
  const rawKeys = new Set(Object.keys(raw ?? {}))
28
29
  const body: Record<string, unknown> = {}
29
30
  for (const [key, value] of Object.entries(parsed.data)) {
30
- if (rawKeys.has(key)) body[key] = value
31
+ if (rawKeys.has(key) && PROVIDER_UPDATE_WRITABLE_KEYS.has(key)) body[key] = value
31
32
  }
32
33
 
33
34
  if (!ops.load()[id]) {
@@ -320,6 +320,10 @@ export const ProviderUpdateSchema = z.object({
320
320
  isEnabled: z.boolean().optional(),
321
321
  requiresApiKey: z.boolean().optional(),
322
322
  notes: z.string().max(4000).nullable().optional(),
323
+ id: z.unknown().optional(),
324
+ type: z.unknown().optional(),
325
+ createdAt: z.unknown().optional(),
326
+ updatedAt: z.unknown().optional(),
323
327
  }).strict()
324
328
 
325
329
  /** PUT /documents/:id — note: creating a new revision is a side-effect of