runtime-settings.test.ts
1 import assert from 'node:assert/strict' 2 import fs from 'node:fs' 3 import os from 'node:os' 4 import path from 'node:path' 5 import { spawnSync } from 'node:child_process' 6 import { describe, it } from 'node:test' 7 8 const repoRoot = path.resolve(path.dirname(new URL(import.meta.url).pathname), '../../..') 9 10 function runWithTempDataDir(script: string) { 11 const tempDir = fs.mkdtempSync(path.join(os.tmpdir(), 'swarmclaw-runtime-settings-')) 12 try { 13 const result = spawnSync(process.execPath, ['--import', 'tsx', '--input-type=module', '--eval', script], { 14 cwd: repoRoot, 15 env: { 16 ...process.env, 17 DATA_DIR: tempDir, 18 WORKSPACE_DIR: path.join(tempDir, 'workspace'), 19 }, 20 encoding: 'utf-8', 21 }) 22 assert.equal(result.status, 0, result.stderr || result.stdout || 'subprocess failed') 23 const lines = (result.stdout || '') 24 .trim() 25 .split('\n') 26 .map((line) => line.trim()) 27 .filter(Boolean) 28 const jsonLine = [...lines].reverse().find((line) => line.startsWith('{')) 29 return JSON.parse(jsonLine || '{}') 30 } finally { 31 fs.rmSync(tempDir, { recursive: true, force: true }) 32 } 33 } 34 35 describe('runtime settings defaults', () => { 36 it('backfills explicit runtime defaults for clean installs', () => { 37 const output = runWithTempDataDir(` 38 const storageMod = await import('@/lib/server/storage') 39 const runtimeMod = await import('@/lib/server/runtime/runtime-settings') 40 const storage = storageMod.default || storageMod 41 const runtime = runtimeMod.default || runtimeMod 42 console.log(JSON.stringify({ 43 settings: storage.loadSettings(), 44 runtime: runtime.loadRuntimeSettings(), 45 })) 46 `) 47 48 assert.equal(output.settings.loopMode, 'bounded') 49 assert.equal(output.settings.agentLoopRecursionLimit, 300) 50 assert.equal(output.settings.ongoingLoopMaxIterations, 250) 51 assert.equal(output.settings.ongoingLoopMaxRuntimeMinutes, 60) 52 assert.equal(output.settings.delegationMaxDepth, 3) 53 assert.equal(output.settings.shellCommandTimeoutSec, 120) 54 assert.equal(output.settings.claudeCodeTimeoutSec, 1800) 55 assert.equal(output.settings.cliProcessTimeoutSec, 1800) 56 assert.equal(output.settings.heartbeatIntervalSec, 1800) 57 assert.equal(output.settings.heartbeatAckMaxChars, 300) 58 assert.equal(output.settings.heartbeatShowOk, false) 59 assert.equal(output.settings.heartbeatShowAlerts, true) 60 assert.equal(output.settings.heartbeatTarget, null) 61 assert.equal(output.settings.heartbeatPrompt, null) 62 assert.equal(output.runtime.agentLoopRecursionLimit, 300) 63 }) 64 65 it('clamps invalid persisted runtime settings into the supported range', () => { 66 const output = runWithTempDataDir(` 67 const storageMod = await import('@/lib/server/storage') 68 const runtimeMod = await import('@/lib/server/runtime/runtime-settings') 69 const storage = storageMod.default || storageMod 70 const runtime = runtimeMod.default || runtimeMod 71 72 storage.saveSettings({ 73 loopMode: 'invalid', 74 agentLoopRecursionLimit: 999, 75 ongoingLoopMaxIterations: 999999, 76 ongoingLoopMaxRuntimeMinutes: -1, 77 delegationMaxDepth: 99, 78 shellCommandTimeoutSec: 0, 79 claudeCodeTimeoutSec: 999999, 80 cliProcessTimeoutSec: 'abc', 81 heartbeatIntervalSec: 999999, 82 heartbeatAckMaxChars: -50, 83 heartbeatShowOk: 'yes', 84 heartbeatShowAlerts: 'off', 85 heartbeatTarget: ' ', 86 heartbeatPrompt: ' ', 87 }) 88 89 console.log(JSON.stringify({ 90 settings: storage.loadSettings(), 91 runtime: runtime.loadRuntimeSettings(), 92 })) 93 `) 94 95 assert.equal(output.settings.loopMode, 'bounded') 96 assert.equal(output.settings.agentLoopRecursionLimit, 500) 97 assert.equal(output.settings.ongoingLoopMaxIterations, 5000) 98 assert.equal(output.settings.ongoingLoopMaxRuntimeMinutes, 0) 99 assert.equal(output.settings.delegationMaxDepth, 12) 100 assert.equal(output.settings.shellCommandTimeoutSec, 1) 101 assert.equal(output.settings.claudeCodeTimeoutSec, 7200) 102 assert.equal(output.settings.cliProcessTimeoutSec, 1800) 103 assert.equal(output.settings.heartbeatIntervalSec, 86400) 104 assert.equal(output.settings.heartbeatAckMaxChars, 0) 105 assert.equal(output.settings.heartbeatShowOk, true) 106 assert.equal(output.settings.heartbeatShowAlerts, false) 107 assert.equal(output.settings.heartbeatTarget, null) 108 assert.equal(output.settings.heartbeatPrompt, null) 109 assert.equal(output.runtime.ongoingLoopMaxRuntimeMs, null) 110 }) 111 })