fix: harden talk silence timeout parsing (#39607) (thanks @danodoesdesign)

Co-authored-by: dano does design <dano.does.design@gmail.com>
This commit is contained in:
Peter Steinberger
2026-03-08 14:29:42 +00:00
parent 6ff7e8f42e
commit 0af3118d08
5 changed files with 38 additions and 18 deletions

View File

@@ -9809,49 +9809,49 @@
"filename": "docs/gateway/configuration-reference.md",
"hashed_secret": "7f8aaf142ce0552c260f2e546dda43ddd7c9aef3",
"is_verified": false,
"line_number": 1813
"line_number": 1815
},
{
"type": "Secret Keyword",
"filename": "docs/gateway/configuration-reference.md",
"hashed_secret": "22af290a1a3d5e941193a41a3d3a9e4ca8da5e27",
"is_verified": false,
"line_number": 1986
"line_number": 1988
},
{
"type": "Secret Keyword",
"filename": "docs/gateway/configuration-reference.md",
"hashed_secret": "ec3810e10fb78db55ce38b9c18d1c3eb1db739e0",
"is_verified": false,
"line_number": 2042
"line_number": 2044
},
{
"type": "Secret Keyword",
"filename": "docs/gateway/configuration-reference.md",
"hashed_secret": "c1e6ee547fd492df1441ac492e8bb294974712bd",
"is_verified": false,
"line_number": 2274
"line_number": 2276
},
{
"type": "Secret Keyword",
"filename": "docs/gateway/configuration-reference.md",
"hashed_secret": "45d676e7c6ab44cf4b8fa366ef2d8fccd3e6d6e6",
"is_verified": false,
"line_number": 2402
"line_number": 2404
},
{
"type": "Secret Keyword",
"filename": "docs/gateway/configuration-reference.md",
"hashed_secret": "a219d7693c25cd2d93313512e200ff3eb374d281",
"is_verified": false,
"line_number": 2655
"line_number": 2657
},
{
"type": "Secret Keyword",
"filename": "docs/gateway/configuration-reference.md",
"hashed_secret": "b6f56e5e92078ed7c078c46fbfeedcbe5719bc25",
"is_verified": false,
"line_number": 2657
"line_number": 2659
}
],
"docs/gateway/configuration.md": [
@@ -10198,21 +10198,21 @@
"filename": "docs/tools/web.md",
"hashed_secret": "6b26c117c66a0c030e239eef595c1e18865132a8",
"is_verified": false,
"line_number": 90
"line_number": 129
},
{
"type": "Secret Keyword",
"filename": "docs/tools/web.md",
"hashed_secret": "491d458f895b9213facb2ee9375b1b044eaea3ac",
"is_verified": false,
"line_number": 179
"line_number": 202
},
{
"type": "Secret Keyword",
"filename": "docs/tools/web.md",
"hashed_secret": "674397e2c0c2faaa85961c708d2a96a7cc7af217",
"is_verified": false,
"line_number": 277
"line_number": 303
}
],
"docs/tts.md": [
@@ -11583,7 +11583,7 @@
"filename": "src/agents/pi-embedded-runner/model.ts",
"hashed_secret": "e774aaeac31c6272107ba89080295e277050fa7c",
"is_verified": false,
"line_number": 272
"line_number": 267
}
],
"src/agents/pi-embedded-runner/run.overflow-compaction.mocks.shared.ts": [
@@ -11680,7 +11680,7 @@
"filename": "src/agents/tools/web-search.ts",
"hashed_secret": "dfba7aade0868074c2861c98e2a9a92f3178a51b",
"is_verified": false,
"line_number": 254
"line_number": 266
}
],
"src/agents/tools/web-tools.enabled-defaults.e2e.test.ts": [
@@ -12335,14 +12335,14 @@
"filename": "src/config/schema.help.ts",
"hashed_secret": "9f4cda226d3868676ac7f86f59e4190eb94bd208",
"is_verified": false,
"line_number": 649
"line_number": 651
},
{
"type": "Secret Keyword",
"filename": "src/config/schema.help.ts",
"hashed_secret": "01822c8bbf6a8b136944b14182cb885100ec2eae",
"is_verified": false,
"line_number": 680
"line_number": 684
}
],
"src/config/schema.irc.ts": [
@@ -12388,7 +12388,7 @@
"filename": "src/config/schema.labels.ts",
"hashed_secret": "2eda7cd978f39eebec3bf03e4410a40e14167fff",
"is_verified": false,
"line_number": 324
"line_number": 325
}
],
"src/config/slack-http-config.test.ts": [
@@ -13034,5 +13034,5 @@
}
]
},
"generated_at": "2026-03-08T13:52:40Z"
"generated_at": "2026-03-08T14:28:30Z"
}

View File

@@ -107,7 +107,9 @@ class TalkModeManager(
}
internal fun resolvedSilenceTimeoutMs(talk: JsonObject?): Long {
val timeout = talk?.get("silenceTimeoutMs").asDoubleOrNull() ?: return defaultSilenceTimeoutMs
val primitive = talk?.get("silenceTimeoutMs") as? JsonPrimitive ?: return defaultSilenceTimeoutMs
if (primitive.isString) return defaultSilenceTimeoutMs
val timeout = primitive.content.toDoubleOrNull() ?: return defaultSilenceTimeoutMs
if (timeout <= 0 || timeout % 1.0 != 0.0 || timeout > Long.MAX_VALUE.toDouble()) {
return defaultSilenceTimeoutMs
}

View File

@@ -73,4 +73,11 @@ class TalkModeConfigParsingTest {
assertEquals(700L, TalkModeManager.resolvedSilenceTimeoutMs(talk))
}
@Test
fun defaultsSilenceTimeoutMsWhenString() {
val talk = buildJsonObject { put("silenceTimeoutMs", "1500") }
assertEquals(700L, TalkModeManager.resolvedSilenceTimeoutMs(talk))
}
}

View File

@@ -98,7 +98,7 @@ final class TalkModeManager: NSObject {
private var gateway: GatewayNodeSession?
private var gatewayConnected = false
private var silenceWindow: TimeInterval = TimeInterval(Self.defaultSilenceTimeoutMs) / 1000
private var silenceWindow: TimeInterval = TimeInterval(TalkModeManager.defaultSilenceTimeoutMs) / 1000
private var lastAudioActivity: Date?
private var noiseFloorSamples: [Double] = []
private var noiseFloor: Double?
@@ -2010,6 +2010,9 @@ extension TalkModeManager {
where timeout > 0 && timeout.rounded(.towardZero) == timeout && timeout <= Double(Int.max):
return Int(timeout)
case let timeout as NSNumber:
if CFGetTypeID(timeout) == CFBooleanGetTypeID() {
return Self.defaultSilenceTimeoutMs
}
let value = timeout.doubleValue
if value > 0 && value.rounded(.towardZero) == value && value <= Double(Int.max) {
return Int(value)

View File

@@ -67,4 +67,12 @@ import Testing
#expect(TalkModeManager.resolvedSilenceTimeoutMs(talk) == 900)
}
@Test func defaultsSilenceTimeoutMsWhenBool() {
let talk: [String: Any] = [
"silenceTimeoutMs": true,
]
#expect(TalkModeManager.resolvedSilenceTimeoutMs(talk) == 900)
}
}