@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.
|
|
154
|
+
## v1.9.27 Highlights
|
|
155
155
|
|
|
156
|
-
|
|
156
|
+
Desktop compatibility and provider-save repair for Intel Mac users and OpenRouter setup.
|
|
157
157
|
|
|
158
|
-
- **
|
|
159
|
-
- **
|
|
160
|
-
- **
|
|
161
|
-
- **Regression coverage.**
|
|
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.
|
|
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
|
|
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({
|
|
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-
|
|
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
|