fix(acp): bump control-message timeout 30s→60s for db-contended hosts (#61)

Field-reported (#61): under realistic concurrency where the
Hermes gateway is also running, state.db lock contention
(Discord sync / skill registration / cron scheduling all
holding write locks) stalls ACP's `initialize` / `session/new` /
`session/load` past the previous 30s watchdog, surfacing as
"Starting…" indefinitely or an opaque timeout error.

SQLite contention on a healthy host clears in seconds, so 60s
gives the lock-resolution path room to breathe while still
surfacing genuinely broken transports promptly. `session/prompt`
remains untimed (it streams events and can run for minutes).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
Alan Wizemann
2026-05-01 15:40:33 +02:00
parent 254af46e93
commit 381adfd925
@@ -362,10 +362,17 @@ public actor ACPClient {
#endif
// session/prompt streams events and can run for minutes no hard
// timeout. Control messages get a 30s watchdog.
// timeout. Control messages get a 60s watchdog. Older versions
// capped at 30s, which the field reported (#61) was tripping
// under realistic gateway+ACP concurrency: the gateway holds
// state.db locks for Discord sync / skill registration / cron
// scheduling, and ACP's `initialize` / `session/new` /
// `session/load` stall waiting for the lock. SQLite contention
// on a healthy host clears in seconds; 60s gives that headroom
// while still surfacing genuinely broken transports promptly.
let timeoutTask: Task<Void, Error>? = if method != "session/prompt" {
Task { [weak self] in
try await Task.sleep(nanoseconds: 30 * 1_000_000_000)
try await Task.sleep(nanoseconds: 60 * 1_000_000_000)
await self?.timeoutRequest(id: requestId, method: method)
}
} else {