mirror of
https://github.com/herdrdev/herdr.git
synced 2026-11-06 16:06:22 +00:00
fix: decode windows agent hook stdin as utf-8 (#5270)
Every bundled Windows PowerShell hook read its hook JSON with
`[Console]::In.ReadToEnd()`, which decodes stdin using the process console
input code page instead of UTF-8. On Korean Windows (CP949) that corrupts
the payload, `ConvertFrom-Json` throws, and the hook silently exits without
reporting the session. Read the raw stdin stream as UTF-8 (BOM-aware) in
every PowerShell hook and bump the affected integration versions.
refs #5253
(cherry picked from commit 00584a2794)
This commit is contained in:
@@ -2,7 +2,7 @@
|
||||
# managed by herdr; reinstalling or updating the integration overwrites this file.
|
||||
# add custom hooks beside this file instead of editing it.
|
||||
# HERDR_INTEGRATION_ID=antigravity_cli
|
||||
# HERDR_INTEGRATION_VERSION=3
|
||||
# HERDR_INTEGRATION_VERSION=4
|
||||
|
||||
# Session-only: this hook reports the Antigravity conversation so Herdr can
|
||||
# resume the pane. Lifecycle state comes from Herdr's screen detection.
|
||||
@@ -20,7 +20,10 @@ if ($Action -ne "session") { Exit-Hook }
|
||||
if ($env:HERDR_ENV -ne "1") { Exit-Hook }
|
||||
if ([string]::IsNullOrWhiteSpace($env:HERDR_PANE_ID)) { Exit-Hook }
|
||||
|
||||
$inputText = [Console]::In.ReadToEnd()
|
||||
# Read hook input as raw bytes so UTF-8 payloads decode correctly even when the
|
||||
# console input code page is not UTF-8 (for example CP949 on Korean Windows).
|
||||
$stdinReader = New-Object System.IO.StreamReader([Console]::OpenStandardInput(), [System.Text.Encoding]::UTF8)
|
||||
$inputText = $stdinReader.ReadToEnd()
|
||||
try {
|
||||
$payload = if ([string]::IsNullOrWhiteSpace($inputText)) { $null } else { $inputText | ConvertFrom-Json }
|
||||
} catch {
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
# managed by herdr; reinstalling or updating the integration overwrites this file.
|
||||
# add custom hooks beside this file instead of editing it.
|
||||
# HERDR_INTEGRATION_ID=antigravity_cli
|
||||
# HERDR_INTEGRATION_VERSION=3
|
||||
# HERDR_INTEGRATION_VERSION=4
|
||||
|
||||
# Session-only: this hook reports the Antigravity conversation so Herdr can
|
||||
# resume the pane. Lifecycle state comes from Herdr's screen detection.
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
# managed by herdr; reinstalling or updating the integration overwrites this file.
|
||||
# add custom hooks beside this file instead of editing it.
|
||||
# HERDR_INTEGRATION_ID=claude
|
||||
# HERDR_INTEGRATION_VERSION=10
|
||||
# HERDR_INTEGRATION_VERSION=11
|
||||
|
||||
param([string]$Action = "")
|
||||
|
||||
@@ -10,7 +10,10 @@ if ($Action -ne "session") { exit 0 }
|
||||
if ($env:HERDR_ENV -ne "1") { exit 0 }
|
||||
if ([string]::IsNullOrWhiteSpace($env:HERDR_PANE_ID)) { exit 0 }
|
||||
|
||||
$inputText = [Console]::In.ReadToEnd()
|
||||
# Read hook input as raw bytes so UTF-8 payloads decode correctly even when the
|
||||
# console input code page is not UTF-8 (for example CP949 on Korean Windows).
|
||||
$stdinReader = New-Object System.IO.StreamReader([Console]::OpenStandardInput(), [System.Text.Encoding]::UTF8)
|
||||
$inputText = $stdinReader.ReadToEnd()
|
||||
try {
|
||||
$payload = if ([string]::IsNullOrWhiteSpace($inputText)) { $null } else { $inputText | ConvertFrom-Json }
|
||||
} catch {
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
# managed by herdr; reinstalling or updating the integration overwrites this file.
|
||||
# add custom hooks beside this file instead of editing it.
|
||||
# HERDR_INTEGRATION_ID=claude
|
||||
# HERDR_INTEGRATION_VERSION=10
|
||||
# HERDR_INTEGRATION_VERSION=11
|
||||
|
||||
set -eu
|
||||
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
# managed by herdr; reinstalling or updating the integration overwrites this file.
|
||||
# add custom hooks beside this file instead of editing it.
|
||||
# HERDR_INTEGRATION_ID=codex
|
||||
# HERDR_INTEGRATION_VERSION=9
|
||||
# HERDR_INTEGRATION_VERSION=10
|
||||
|
||||
param([string]$Action = "")
|
||||
|
||||
@@ -10,7 +10,10 @@ if ($Action -ne "session") { exit 0 }
|
||||
if ($env:HERDR_ENV -ne "1") { exit 0 }
|
||||
if ([string]::IsNullOrWhiteSpace($env:HERDR_PANE_ID)) { exit 0 }
|
||||
|
||||
$inputText = [Console]::In.ReadToEnd()
|
||||
# Read hook input as raw bytes so UTF-8 payloads decode correctly even when the
|
||||
# console input code page is not UTF-8 (for example CP949 on Korean Windows).
|
||||
$stdinReader = New-Object System.IO.StreamReader([Console]::OpenStandardInput(), [System.Text.Encoding]::UTF8)
|
||||
$inputText = $stdinReader.ReadToEnd()
|
||||
try {
|
||||
$payload = if ([string]::IsNullOrWhiteSpace($inputText)) { $null } else { $inputText | ConvertFrom-Json }
|
||||
} catch {
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
# managed by herdr; reinstalling or updating the integration overwrites this file.
|
||||
# add custom hooks beside this file instead of editing it.
|
||||
# HERDR_INTEGRATION_ID=codex
|
||||
# HERDR_INTEGRATION_VERSION=9
|
||||
# HERDR_INTEGRATION_VERSION=10
|
||||
|
||||
set -eu
|
||||
|
||||
|
||||
@@ -2,13 +2,16 @@
|
||||
# managed by herdr; reinstalling or updating the integration overwrites this file.
|
||||
# add custom hooks beside this file instead of editing it.
|
||||
# HERDR_INTEGRATION_ID=copilot
|
||||
# HERDR_INTEGRATION_VERSION=3
|
||||
# HERDR_INTEGRATION_VERSION=4
|
||||
|
||||
if ($env:HERDR_ENV -ne "1") { exit 0 }
|
||||
if ([string]::IsNullOrWhiteSpace($env:HERDR_PANE_ID)) { exit 0 }
|
||||
if ([string]::IsNullOrWhiteSpace($env:HERDR_SOCKET_PATH)) { exit 0 }
|
||||
|
||||
$inputText = [Console]::In.ReadToEnd()
|
||||
# Read hook input as raw bytes so UTF-8 payloads decode correctly even when the
|
||||
# console input code page is not UTF-8 (for example CP949 on Korean Windows).
|
||||
$stdinReader = New-Object System.IO.StreamReader([Console]::OpenStandardInput(), [System.Text.Encoding]::UTF8)
|
||||
$inputText = $stdinReader.ReadToEnd()
|
||||
try {
|
||||
$payload = if ([string]::IsNullOrWhiteSpace($inputText)) { @{} } else { $inputText | ConvertFrom-Json }
|
||||
} catch {
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
# managed by herdr; reinstalling or updating the integration overwrites this file.
|
||||
# add custom hooks beside this file instead of editing it.
|
||||
# HERDR_INTEGRATION_ID=copilot
|
||||
# HERDR_INTEGRATION_VERSION=3
|
||||
# HERDR_INTEGRATION_VERSION=4
|
||||
|
||||
set -eu
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# managed by herdr; reinstalling the integration replaces this file.
|
||||
# HERDR_INTEGRATION_ID=cursor
|
||||
# HERDR_INTEGRATION_VERSION=1
|
||||
# HERDR_INTEGRATION_VERSION=2
|
||||
|
||||
param([string]$Action = "")
|
||||
|
||||
@@ -13,7 +13,10 @@ if ($Action -ne "session") { Exit-Hook }
|
||||
if ($env:HERDR_ENV -ne "1") { Exit-Hook }
|
||||
if ([string]::IsNullOrWhiteSpace($env:HERDR_PANE_ID)) { Exit-Hook }
|
||||
|
||||
$inputText = [Console]::In.ReadToEnd()
|
||||
# Read hook input as raw bytes so UTF-8 payloads decode correctly even when the
|
||||
# console input code page is not UTF-8 (for example CP949 on Korean Windows).
|
||||
$stdinReader = New-Object System.IO.StreamReader([Console]::OpenStandardInput(), [System.Text.Encoding]::UTF8)
|
||||
$inputText = $stdinReader.ReadToEnd()
|
||||
$jsonStart = $inputText.IndexOf("{")
|
||||
if ($jsonStart -gt 0) {
|
||||
$inputText = $inputText.Substring($jsonStart)
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
#!/bin/sh
|
||||
# managed by herdr; reinstalling the integration replaces this file.
|
||||
# HERDR_INTEGRATION_ID=cursor
|
||||
# HERDR_INTEGRATION_VERSION=1
|
||||
# HERDR_INTEGRATION_VERSION=2
|
||||
|
||||
[ "${1:-}" = "session" ] || exit 0
|
||||
[ "${HERDR_ENV:-}" = "1" ] || exit 0
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
# managed by herdr; reinstalling or updating the integration overwrites this file.
|
||||
# add custom hooks beside this file instead of editing it.
|
||||
# HERDR_INTEGRATION_ID=devin
|
||||
# HERDR_INTEGRATION_VERSION=2
|
||||
# HERDR_INTEGRATION_VERSION=3
|
||||
|
||||
param([string]$Action = "")
|
||||
|
||||
@@ -10,7 +10,10 @@ if ($Action -ne "session") { exit 0 }
|
||||
if ($env:HERDR_ENV -ne "1") { exit 0 }
|
||||
if ([string]::IsNullOrWhiteSpace($env:HERDR_PANE_ID)) { exit 0 }
|
||||
|
||||
$inputText = [Console]::In.ReadToEnd()
|
||||
# Read hook input as raw bytes so UTF-8 payloads decode correctly even when the
|
||||
# console input code page is not UTF-8 (for example CP949 on Korean Windows).
|
||||
$stdinReader = New-Object System.IO.StreamReader([Console]::OpenStandardInput(), [System.Text.Encoding]::UTF8)
|
||||
$inputText = $stdinReader.ReadToEnd()
|
||||
try {
|
||||
$payload = if ([string]::IsNullOrWhiteSpace($inputText)) { $null } else { $inputText | ConvertFrom-Json }
|
||||
} catch {
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
# managed by herdr; reinstalling or updating the integration overwrites this file.
|
||||
# add custom hooks beside this file instead of editing it.
|
||||
# HERDR_INTEGRATION_ID=devin
|
||||
# HERDR_INTEGRATION_VERSION=2
|
||||
# HERDR_INTEGRATION_VERSION=3
|
||||
|
||||
set -eu
|
||||
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
# managed by herdr; reinstalling or updating the integration overwrites this file.
|
||||
# add custom hooks beside this file instead of editing it.
|
||||
# HERDR_INTEGRATION_ID=droid
|
||||
# HERDR_INTEGRATION_VERSION=3
|
||||
# HERDR_INTEGRATION_VERSION=4
|
||||
|
||||
param([string]$Action = "")
|
||||
|
||||
@@ -10,7 +10,10 @@ if ($Action -ne "session") { exit 0 }
|
||||
if ($env:HERDR_ENV -ne "1") { exit 0 }
|
||||
if ([string]::IsNullOrWhiteSpace($env:HERDR_PANE_ID)) { exit 0 }
|
||||
|
||||
$inputText = [Console]::In.ReadToEnd()
|
||||
# Read hook input as raw bytes so UTF-8 payloads decode correctly even when the
|
||||
# console input code page is not UTF-8 (for example CP949 on Korean Windows).
|
||||
$stdinReader = New-Object System.IO.StreamReader([Console]::OpenStandardInput(), [System.Text.Encoding]::UTF8)
|
||||
$inputText = $stdinReader.ReadToEnd()
|
||||
try {
|
||||
$payload = if ([string]::IsNullOrWhiteSpace($inputText)) { $null } else { $inputText | ConvertFrom-Json }
|
||||
} catch {
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
# managed by herdr; reinstalling or updating the integration overwrites this file.
|
||||
# add custom hooks beside this file instead of editing it.
|
||||
# HERDR_INTEGRATION_ID=droid
|
||||
# HERDR_INTEGRATION_VERSION=3
|
||||
# HERDR_INTEGRATION_VERSION=4
|
||||
|
||||
set -eu
|
||||
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
# managed by herdr; reinstalling or updating the integration overwrites this file.
|
||||
# add custom hooks beside this file instead of editing it.
|
||||
# HERDR_INTEGRATION_ID=grok
|
||||
# HERDR_INTEGRATION_VERSION=2
|
||||
# HERDR_INTEGRATION_VERSION=3
|
||||
|
||||
param([string]$Action = "")
|
||||
|
||||
@@ -10,7 +10,10 @@ if ($Action -ne "session") { exit 0 }
|
||||
if ($env:HERDR_ENV -ne "1") { exit 0 }
|
||||
if ([string]::IsNullOrWhiteSpace($env:HERDR_PANE_ID)) { exit 0 }
|
||||
|
||||
$inputText = [Console]::In.ReadToEnd()
|
||||
# Read hook input as raw bytes so UTF-8 payloads decode correctly even when the
|
||||
# console input code page is not UTF-8 (for example CP949 on Korean Windows).
|
||||
$stdinReader = New-Object System.IO.StreamReader([Console]::OpenStandardInput(), [System.Text.Encoding]::UTF8)
|
||||
$inputText = $stdinReader.ReadToEnd()
|
||||
try {
|
||||
$payload = if ([string]::IsNullOrWhiteSpace($inputText)) { $null } else { $inputText | ConvertFrom-Json }
|
||||
} catch {
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
# managed by herdr; reinstalling or updating the integration overwrites this file.
|
||||
# add custom hooks beside this file instead of editing it.
|
||||
# HERDR_INTEGRATION_ID=grok
|
||||
# HERDR_INTEGRATION_VERSION=2
|
||||
# HERDR_INTEGRATION_VERSION=3
|
||||
|
||||
set -eu
|
||||
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
# managed by herdr; reinstalling or updating the integration overwrites this file.
|
||||
# add custom hooks beside this file instead of editing it.
|
||||
# HERDR_INTEGRATION_ID=kimi
|
||||
# HERDR_INTEGRATION_VERSION=7
|
||||
# HERDR_INTEGRATION_VERSION=8
|
||||
|
||||
param([string]$Action = "")
|
||||
|
||||
@@ -10,7 +10,10 @@ if (@("session", "working", "blocked", "idle") -notcontains $Action) { exit 0 }
|
||||
if ($env:HERDR_ENV -ne "1") { exit 0 }
|
||||
if ([string]::IsNullOrWhiteSpace($env:HERDR_PANE_ID)) { exit 0 }
|
||||
|
||||
$inputText = [Console]::In.ReadToEnd()
|
||||
# Read hook input as raw bytes so UTF-8 payloads decode correctly even when the
|
||||
# console input code page is not UTF-8 (for example CP949 on Korean Windows).
|
||||
$stdinReader = New-Object System.IO.StreamReader([Console]::OpenStandardInput(), [System.Text.Encoding]::UTF8)
|
||||
$inputText = $stdinReader.ReadToEnd()
|
||||
try {
|
||||
$payload = if ([string]::IsNullOrWhiteSpace($inputText)) { $null } else { $inputText | ConvertFrom-Json }
|
||||
} catch {
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
#!/bin/sh
|
||||
# managed by herdr; reinstalling the integration replaces this file.
|
||||
# HERDR_INTEGRATION_ID=kimi
|
||||
# HERDR_INTEGRATION_VERSION=7
|
||||
# HERDR_INTEGRATION_VERSION=8
|
||||
|
||||
action="${1:-}"
|
||||
case "$action" in
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# managed by herdr; reinstalling the integration replaces this file.
|
||||
# HERDR_INTEGRATION_ID=letta
|
||||
# HERDR_INTEGRATION_VERSION=1
|
||||
# HERDR_INTEGRATION_VERSION=2
|
||||
|
||||
param([string]$Action = "")
|
||||
|
||||
@@ -9,7 +9,10 @@ if ($env:HERDR_ENV -ne "1") { exit 0 }
|
||||
if ([string]::IsNullOrWhiteSpace($env:HERDR_PANE_ID)) { exit 0 }
|
||||
if ([string]::IsNullOrWhiteSpace($env:HERDR_SOCKET_PATH)) { exit 0 }
|
||||
|
||||
$inputText = [Console]::In.ReadToEnd()
|
||||
# Read hook input as raw bytes so UTF-8 payloads decode correctly even when the
|
||||
# console input code page is not UTF-8 (for example CP949 on Korean Windows).
|
||||
$stdinReader = New-Object System.IO.StreamReader([Console]::OpenStandardInput(), [System.Text.Encoding]::UTF8)
|
||||
$inputText = $stdinReader.ReadToEnd()
|
||||
try {
|
||||
$payload = if ([string]::IsNullOrWhiteSpace($inputText)) { $null } else { $inputText | ConvertFrom-Json }
|
||||
} catch {
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
#!/bin/sh
|
||||
# managed by herdr; reinstalling the integration replaces this file.
|
||||
# HERDR_INTEGRATION_ID=letta
|
||||
# HERDR_INTEGRATION_VERSION=1
|
||||
# HERDR_INTEGRATION_VERSION=2
|
||||
|
||||
[ "${1:-}" = "session" ] || exit 0
|
||||
[ "${HERDR_ENV:-}" = "1" ] || exit 0
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
# managed by herdr; reinstalling or updating the integration overwrites this file.
|
||||
# add custom hooks beside this file instead of editing it.
|
||||
# HERDR_INTEGRATION_ID=mastracode
|
||||
# HERDR_INTEGRATION_VERSION=2
|
||||
# HERDR_INTEGRATION_VERSION=3
|
||||
|
||||
param([string]$Action = "")
|
||||
|
||||
@@ -10,7 +10,10 @@ if ($Action -notin @("session", "working", "idle", "blocked")) { exit 0 }
|
||||
if ($env:HERDR_ENV -ne "1") { exit 0 }
|
||||
if ([string]::IsNullOrWhiteSpace($env:HERDR_PANE_ID)) { exit 0 }
|
||||
|
||||
$inputText = [Console]::In.ReadToEnd()
|
||||
# Read hook input as raw bytes so UTF-8 payloads decode correctly even when the
|
||||
# console input code page is not UTF-8 (for example CP949 on Korean Windows).
|
||||
$stdinReader = New-Object System.IO.StreamReader([Console]::OpenStandardInput(), [System.Text.Encoding]::UTF8)
|
||||
$inputText = $stdinReader.ReadToEnd()
|
||||
try {
|
||||
$payload = if ([string]::IsNullOrWhiteSpace($inputText)) { $null } else { $inputText | ConvertFrom-Json }
|
||||
} catch {
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
# managed by herdr; reinstalling or updating the integration overwrites this file.
|
||||
# add custom hooks beside this file instead of editing it.
|
||||
# HERDR_INTEGRATION_ID=mastracode
|
||||
# HERDR_INTEGRATION_VERSION=2
|
||||
# HERDR_INTEGRATION_VERSION=3
|
||||
|
||||
set -eu
|
||||
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
# managed by herdr; reinstalling or updating the integration overwrites this file.
|
||||
# add custom hooks beside this file instead of editing it.
|
||||
# HERDR_INTEGRATION_ID=qodercli
|
||||
# HERDR_INTEGRATION_VERSION=3
|
||||
# HERDR_INTEGRATION_VERSION=4
|
||||
|
||||
param([string]$Action = "")
|
||||
|
||||
@@ -10,7 +10,10 @@ if ($Action -ne "session") { exit 0 }
|
||||
if ($env:HERDR_ENV -ne "1") { exit 0 }
|
||||
if ([string]::IsNullOrWhiteSpace($env:HERDR_PANE_ID)) { exit 0 }
|
||||
|
||||
$inputText = [Console]::In.ReadToEnd()
|
||||
# Read hook input as raw bytes so UTF-8 payloads decode correctly even when the
|
||||
# console input code page is not UTF-8 (for example CP949 on Korean Windows).
|
||||
$stdinReader = New-Object System.IO.StreamReader([Console]::OpenStandardInput(), [System.Text.Encoding]::UTF8)
|
||||
$inputText = $stdinReader.ReadToEnd()
|
||||
try {
|
||||
$payload = if ([string]::IsNullOrWhiteSpace($inputText)) { $null } else { $inputText | ConvertFrom-Json }
|
||||
} catch {
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
#!/bin/sh
|
||||
# managed by herdr; reinstalling the integration replaces this file.
|
||||
# HERDR_INTEGRATION_ID=qodercli
|
||||
# HERDR_INTEGRATION_VERSION=3
|
||||
# HERDR_INTEGRATION_VERSION=4
|
||||
|
||||
[ "${1:-}" = "session" ] || exit 0
|
||||
[ "${HERDR_ENV:-}" = "1" ] || exit 0
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
# managed by herdr; reinstalling or updating the integration overwrites this file.
|
||||
# add custom hooks beside this file instead of editing it.
|
||||
# HERDR_INTEGRATION_ID=qwen
|
||||
# HERDR_INTEGRATION_VERSION=1
|
||||
# HERDR_INTEGRATION_VERSION=2
|
||||
|
||||
param([string]$Action = "")
|
||||
|
||||
@@ -11,7 +11,10 @@ if ($env:HERDR_ENV -ne "1") { exit 0 }
|
||||
if ([string]::IsNullOrWhiteSpace($env:HERDR_PANE_ID)) { exit 0 }
|
||||
if ([string]::IsNullOrWhiteSpace($env:HERDR_SOCKET_PATH)) { exit 0 }
|
||||
|
||||
$inputText = [Console]::In.ReadToEnd()
|
||||
# Read hook input as raw bytes so UTF-8 payloads decode correctly even when the
|
||||
# console input code page is not UTF-8 (for example CP949 on Korean Windows).
|
||||
$stdinReader = New-Object System.IO.StreamReader([Console]::OpenStandardInput(), [System.Text.Encoding]::UTF8)
|
||||
$inputText = $stdinReader.ReadToEnd()
|
||||
try {
|
||||
$payload = if ([string]::IsNullOrWhiteSpace($inputText)) { $null } else { $inputText | ConvertFrom-Json }
|
||||
} catch {
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
#!/bin/sh
|
||||
# managed by herdr; reinstalling the integration replaces this file.
|
||||
# HERDR_INTEGRATION_ID=qwen
|
||||
# HERDR_INTEGRATION_VERSION=1
|
||||
# HERDR_INTEGRATION_VERSION=2
|
||||
|
||||
[ "${1:-}" = "session" ] || exit 0
|
||||
[ "${HERDR_ENV:-}" = "1" ] || exit 0
|
||||
|
||||
+13
-13
@@ -49,7 +49,7 @@ const CLAUDE_HOOK_ASSET: &str = if cfg!(windows) {
|
||||
} else {
|
||||
include_str!("assets/claude/herdr-agent-state.sh")
|
||||
};
|
||||
const CLAUDE_INTEGRATION_VERSION: u32 = 10;
|
||||
const CLAUDE_INTEGRATION_VERSION: u32 = 11;
|
||||
const CODEX_HOOK_INSTALL_NAME: &str = if cfg!(windows) {
|
||||
"herdr-agent-state.ps1"
|
||||
} else {
|
||||
@@ -60,7 +60,7 @@ const CODEX_HOOK_ASSET: &str = if cfg!(windows) {
|
||||
} else {
|
||||
include_str!("assets/codex/herdr-agent-state.sh")
|
||||
};
|
||||
const CODEX_INTEGRATION_VERSION: u32 = 9;
|
||||
const CODEX_INTEGRATION_VERSION: u32 = 10;
|
||||
const KIMI_HOOK_INSTALL_NAME: &str = if cfg!(windows) {
|
||||
"herdr-agent-state.ps1"
|
||||
} else {
|
||||
@@ -71,7 +71,7 @@ const KIMI_HOOK_ASSET: &str = if cfg!(windows) {
|
||||
} else {
|
||||
include_str!("assets/kimi/herdr-agent-state.sh")
|
||||
};
|
||||
const KIMI_INTEGRATION_VERSION: u32 = 7;
|
||||
const KIMI_INTEGRATION_VERSION: u32 = 8;
|
||||
const KIMI_CONFIG_BLOCK_BEGIN: &str = "# >>> herdr kimi integration";
|
||||
const KIMI_CONFIG_BLOCK_END: &str = "# <<< herdr kimi integration";
|
||||
const KIMI_MIN_VERSION: &str = "0.14.0";
|
||||
@@ -113,7 +113,7 @@ const COPILOT_HOOK_ASSET: &str = if cfg!(windows) {
|
||||
} else {
|
||||
include_str!("assets/copilot/herdr-agent-state.sh")
|
||||
};
|
||||
const COPILOT_INTEGRATION_VERSION: u32 = 3;
|
||||
const COPILOT_INTEGRATION_VERSION: u32 = 4;
|
||||
const COPILOT_HOOK_EVENTS: [&str; 1] = ["SessionStart"];
|
||||
const COPILOT_REMOVED_LIFECYCLE_HOOK_EVENTS: [&str; 9] = [
|
||||
"UserPromptSubmit",
|
||||
@@ -136,7 +136,7 @@ const DEVIN_HOOK_ASSET: &str = if cfg!(windows) {
|
||||
} else {
|
||||
include_str!("assets/devin/herdr-agent-state.sh")
|
||||
};
|
||||
const DEVIN_INTEGRATION_VERSION: u32 = 2;
|
||||
const DEVIN_INTEGRATION_VERSION: u32 = 3;
|
||||
const DEVIN_HOOK_EVENTS: [(&str, &str); 6] = [
|
||||
("SessionStart", "session"),
|
||||
("UserPromptSubmit", "session"),
|
||||
@@ -163,7 +163,7 @@ const DROID_HOOK_ASSET: &str = if cfg!(windows) {
|
||||
} else {
|
||||
include_str!("assets/droid/herdr-agent-state.sh")
|
||||
};
|
||||
const DROID_INTEGRATION_VERSION: u32 = 3;
|
||||
const DROID_INTEGRATION_VERSION: u32 = 4;
|
||||
const DROID_HOOK_EVENTS: [(&str, &str); 1] = [("SessionStart", "session")];
|
||||
const DROID_REMOVED_LIFECYCLE_HOOK_EVENTS: [(&str, &str); 9] = [
|
||||
("SessionStart", "idle"),
|
||||
@@ -204,7 +204,7 @@ const QODERCLI_HOOK_ASSET: &str = if cfg!(windows) {
|
||||
} else {
|
||||
include_str!("assets/qodercli/herdr-agent-state.sh")
|
||||
};
|
||||
const QODERCLI_INTEGRATION_VERSION: u32 = 3;
|
||||
const QODERCLI_INTEGRATION_VERSION: u32 = 4;
|
||||
const QODERCLI_HOOK_EVENTS: [(&str, &str); 1] = [("SessionStart", "session")];
|
||||
const QWEN_HOOK_INSTALL_NAME: &str = if cfg!(windows) {
|
||||
"herdr-agent-session.ps1"
|
||||
@@ -216,7 +216,7 @@ const QWEN_HOOK_ASSET: &str = if cfg!(windows) {
|
||||
} else {
|
||||
include_str!("assets/qwen/herdr-agent-session.sh")
|
||||
};
|
||||
const QWEN_INTEGRATION_VERSION: u32 = 1;
|
||||
const QWEN_INTEGRATION_VERSION: u32 = 2;
|
||||
const QWEN_HOOK_EVENTS: [(&str, &str); 1] = [("SessionStart", "session")];
|
||||
const LETTA_HOOK_INSTALL_NAME: &str = if cfg!(windows) {
|
||||
"herdr-agent-session.ps1"
|
||||
@@ -228,7 +228,7 @@ const LETTA_HOOK_ASSET: &str = if cfg!(windows) {
|
||||
} else {
|
||||
include_str!("assets/letta/herdr-agent-session.sh")
|
||||
};
|
||||
const LETTA_INTEGRATION_VERSION: u32 = 1;
|
||||
const LETTA_INTEGRATION_VERSION: u32 = 2;
|
||||
const LETTA_HOOK_TIMEOUT_MS: u64 = 10_000;
|
||||
const QODERCLI_REMOVED_LIFECYCLE_HOOK_EVENTS: [(&str, &str); 12] = [
|
||||
("SessionStart", "idle"),
|
||||
@@ -254,7 +254,7 @@ const CURSOR_HOOK_ASSET: &str = if cfg!(windows) {
|
||||
} else {
|
||||
include_str!("assets/cursor/herdr-agent-state.sh")
|
||||
};
|
||||
const CURSOR_INTEGRATION_VERSION: u32 = 1;
|
||||
const CURSOR_INTEGRATION_VERSION: u32 = 2;
|
||||
#[cfg(windows)]
|
||||
const ANTIGRAVITY_CLI_HOOK_INSTALL_NAME: &str = "herdr-agent-state.ps1";
|
||||
#[cfg(not(windows))]
|
||||
@@ -265,7 +265,7 @@ const ANTIGRAVITY_CLI_HOOK_ASSET: &str =
|
||||
#[cfg(not(windows))]
|
||||
const ANTIGRAVITY_CLI_HOOK_ASSET: &str =
|
||||
include_str!("assets/antigravity_cli/herdr-agent-state.sh");
|
||||
const ANTIGRAVITY_CLI_INTEGRATION_VERSION: u32 = 3;
|
||||
const ANTIGRAVITY_CLI_INTEGRATION_VERSION: u32 = 4;
|
||||
/// Antigravity CLI keys `hooks.json` by hook name, so every Herdr entry lives
|
||||
/// under one Herdr-owned block that install rewrites and uninstall removes.
|
||||
const ANTIGRAVITY_CLI_HOOK_BLOCK_NAME: &str = "herdr";
|
||||
@@ -291,7 +291,7 @@ const MASTRACODE_HOOK_ASSET: &str = if cfg!(windows) {
|
||||
} else {
|
||||
include_str!("assets/mastracode/herdr-agent-state.sh")
|
||||
};
|
||||
const MASTRACODE_INTEGRATION_VERSION: u32 = 2;
|
||||
const MASTRACODE_INTEGRATION_VERSION: u32 = 3;
|
||||
const MASTRACODE_HOOK_TIMEOUT_MS: u64 = 10_000;
|
||||
const MASTRACODE_REMOVED_HOOK_EVENTS: [(&str, &str); 2] =
|
||||
[("SessionStart", "idle"), ("SessionEnd", "release")];
|
||||
@@ -319,7 +319,7 @@ const GROK_HOOK_ASSET: &str = if cfg!(windows) {
|
||||
} else {
|
||||
include_str!("assets/grok/herdr-agent-state.sh")
|
||||
};
|
||||
const GROK_INTEGRATION_VERSION: u32 = 2;
|
||||
const GROK_INTEGRATION_VERSION: u32 = 3;
|
||||
|
||||
pub(crate) const INSTALL_WARNING_PREFIX: &str = "warning:";
|
||||
|
||||
|
||||
+139
-12
@@ -1132,7 +1132,7 @@ fn claude_v9_integration_status_is_outdated_until_reinstalled() {
|
||||
|
||||
assert_eq!(claude.path, hook_path);
|
||||
assert_eq!(claude.installed_version, Some(9));
|
||||
assert_eq!(claude.expected_version, 10);
|
||||
assert_eq!(claude.expected_version, 11);
|
||||
assert_eq!(claude.state, IntegrationStatusKind::Outdated);
|
||||
|
||||
install_claude().unwrap();
|
||||
@@ -1141,7 +1141,7 @@ fn claude_v9_integration_status_is_outdated_until_reinstalled() {
|
||||
hook_path,
|
||||
CLAUDE_INTEGRATION_VERSION,
|
||||
);
|
||||
assert_eq!(status.installed_version, Some(10));
|
||||
assert_eq!(status.installed_version, Some(11));
|
||||
assert_eq!(status.state, IntegrationStatusKind::Current);
|
||||
|
||||
std::env::remove_var("HOME");
|
||||
@@ -1171,13 +1171,139 @@ fn claude_v2_integration_status_is_outdated() {
|
||||
|
||||
assert_eq!(claude.path, hook_path);
|
||||
assert_eq!(claude.installed_version, Some(2));
|
||||
assert_eq!(claude.expected_version, 10);
|
||||
assert_eq!(claude.expected_version, 11);
|
||||
assert_eq!(claude.state, IntegrationStatusKind::Outdated);
|
||||
|
||||
std::env::remove_var("HOME");
|
||||
let _ = fs::remove_dir_all(base);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn windows_hooks_decode_stdin_as_utf8() {
|
||||
// Windows PowerShell decodes `[Console]::In` with the console input code
|
||||
// page, which corrupts UTF-8 hook payloads on non-UTF-8 locales such as
|
||||
// Korean Windows (CP949) and makes the hook silently drop the session.
|
||||
// Every bundled PowerShell hook must read the raw stdin stream as UTF-8.
|
||||
// See #5253.
|
||||
for (name, asset) in [
|
||||
(
|
||||
"antigravity_cli",
|
||||
include_str!("assets/antigravity_cli/herdr-agent-state.ps1"),
|
||||
),
|
||||
(
|
||||
"claude",
|
||||
include_str!("assets/claude/herdr-agent-state.ps1"),
|
||||
),
|
||||
("codex", include_str!("assets/codex/herdr-agent-state.ps1")),
|
||||
(
|
||||
"copilot",
|
||||
include_str!("assets/copilot/herdr-agent-state.ps1"),
|
||||
),
|
||||
(
|
||||
"cursor",
|
||||
include_str!("assets/cursor/herdr-agent-state.ps1"),
|
||||
),
|
||||
("devin", include_str!("assets/devin/herdr-agent-state.ps1")),
|
||||
("droid", include_str!("assets/droid/herdr-agent-state.ps1")),
|
||||
("grok", include_str!("assets/grok/herdr-agent-state.ps1")),
|
||||
("kimi", include_str!("assets/kimi/herdr-agent-state.ps1")),
|
||||
(
|
||||
"letta",
|
||||
include_str!("assets/letta/herdr-agent-session.ps1"),
|
||||
),
|
||||
(
|
||||
"mastracode",
|
||||
include_str!("assets/mastracode/herdr-agent-state.ps1"),
|
||||
),
|
||||
(
|
||||
"qodercli",
|
||||
include_str!("assets/qodercli/herdr-agent-state.ps1"),
|
||||
),
|
||||
("qwen", include_str!("assets/qwen/herdr-agent-session.ps1")),
|
||||
] {
|
||||
assert!(
|
||||
!asset.contains("[Console]::In.ReadToEnd()"),
|
||||
"{name} hook must not decode stdin with the console input code page"
|
||||
);
|
||||
assert!(
|
||||
asset.contains("[Console]::OpenStandardInput()")
|
||||
&& asset.contains("[System.Text.Encoding]::UTF8"),
|
||||
"{name} hook must decode raw stdin as UTF-8"
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(windows)]
|
||||
#[test]
|
||||
fn claude_windows_hook_reports_utf8_session_path() {
|
||||
use std::io::Write;
|
||||
use std::process::{Command, Stdio};
|
||||
|
||||
let _lock = integration_env_lock();
|
||||
let base = unique_base();
|
||||
let home = base.join("home");
|
||||
fs::create_dir_all(home.join(".claude")).unwrap();
|
||||
let previous_home = std::env::var_os("HOME");
|
||||
std::env::set_var("HOME", &home);
|
||||
let installed = install_claude().unwrap();
|
||||
|
||||
let capture = base.join("args.txt");
|
||||
let fake_herdr = base.join("herdr-capture.ps1");
|
||||
fs::write(
|
||||
&fake_herdr,
|
||||
"$capture = $env:HERDR_TEST_CAPTURE\n[System.IO.File]::WriteAllText($capture, ($args -join \"`n\"), [System.Text.UTF8Encoding]::new($false))\n",
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
// A Korean profile path: decoded with a non-UTF-8 console input code page
|
||||
// (CP949 on Korean Windows) it garbles the JSON and the hook exits silently.
|
||||
let transcript = "C:\\Users\\\u{d55c}\u{ae00}\\transcript.jsonl";
|
||||
let payload = format!(
|
||||
r#"{{"hook_event_name":"SessionStart","session_id":"claude-session","transcript_path":{}}}"#,
|
||||
serde_json::to_string(transcript).unwrap()
|
||||
);
|
||||
|
||||
let mut child = Command::new("powershell")
|
||||
.args(["-NoProfile", "-ExecutionPolicy", "Bypass", "-File"])
|
||||
.arg(&installed.hook_path)
|
||||
.arg("session")
|
||||
.env("HERDR_ENV", "1")
|
||||
.env("HERDR_PANE_ID", "w1:p2")
|
||||
.env("HERDR_BIN_PATH", &fake_herdr)
|
||||
.env("HERDR_TEST_CAPTURE", &capture)
|
||||
.env_remove("CURSOR_VERSION")
|
||||
.stdin(Stdio::piped())
|
||||
.stdout(Stdio::piped())
|
||||
.stderr(Stdio::piped())
|
||||
.spawn()
|
||||
.unwrap();
|
||||
child
|
||||
.stdin
|
||||
.take()
|
||||
.unwrap()
|
||||
.write_all(payload.as_bytes())
|
||||
.unwrap();
|
||||
let output = child.wait_with_output().unwrap();
|
||||
assert!(
|
||||
output.status.success(),
|
||||
"hook failed: stderr={}",
|
||||
String::from_utf8_lossy(&output.stderr)
|
||||
);
|
||||
|
||||
let args = fs::read_to_string(&capture).unwrap();
|
||||
assert!(args.contains("report-agent-session"), "captured: {args}");
|
||||
assert!(args.contains("--agent-session-id"), "captured: {args}");
|
||||
assert!(args.contains("claude-session"), "captured: {args}");
|
||||
assert!(args.contains(transcript), "captured: {args}");
|
||||
|
||||
if let Some(home) = previous_home {
|
||||
std::env::set_var("HOME", home);
|
||||
} else {
|
||||
std::env::remove_var("HOME");
|
||||
}
|
||||
let _ = fs::remove_dir_all(base);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn uninstall_claude_removes_herdr_hooks_and_preserves_others() {
|
||||
let _lock = integration_env_lock();
|
||||
@@ -1304,7 +1430,7 @@ fn codex_v2_integration_status_is_outdated() {
|
||||
|
||||
assert_eq!(codex.path, hook_path);
|
||||
assert_eq!(codex.installed_version, Some(2));
|
||||
assert_eq!(codex.expected_version, 9);
|
||||
assert_eq!(codex.expected_version, 10);
|
||||
assert_eq!(codex.state, IntegrationStatusKind::Outdated);
|
||||
|
||||
std::env::remove_var("HOME");
|
||||
@@ -3873,7 +3999,7 @@ fn install_qwen_writes_session_hook_and_preserves_settings() {
|
||||
assert!(settings.get("permissions").is_some());
|
||||
let hook_asset = fs::read_to_string(&installed.hook_path).unwrap();
|
||||
assert!(hook_asset.contains("HERDR_INTEGRATION_ID=qwen"));
|
||||
assert!(hook_asset.contains("HERDR_INTEGRATION_VERSION=1"));
|
||||
assert!(hook_asset.contains("HERDR_INTEGRATION_VERSION=2"));
|
||||
assert!(hook_asset.contains("herdr:qwen"));
|
||||
|
||||
install_qwen().unwrap();
|
||||
@@ -4251,7 +4377,7 @@ fn install_cursor_uses_cursor_config_dir_env() {
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn cursor_v1_integration_status_is_current() {
|
||||
fn cursor_v1_integration_status_is_outdated() {
|
||||
let _lock = integration_env_lock();
|
||||
let base = unique_base();
|
||||
let cursor_dir = base.join(".cursor");
|
||||
@@ -4269,8 +4395,9 @@ fn cursor_v1_integration_status_is_current() {
|
||||
.iter()
|
||||
.find(|status| status.target == crate::api::schema::IntegrationTarget::Cursor)
|
||||
.expect("cursor integration status");
|
||||
assert_eq!(cursor.state, IntegrationStatusKind::Current);
|
||||
assert_eq!(cursor.installed_version, Some(CURSOR_INTEGRATION_VERSION));
|
||||
assert_eq!(cursor.installed_version, Some(1));
|
||||
assert_eq!(cursor.expected_version, CURSOR_INTEGRATION_VERSION);
|
||||
assert_eq!(cursor.state, IntegrationStatusKind::Outdated);
|
||||
|
||||
clear_integration_path_env();
|
||||
let _ = fs::remove_dir_all(base);
|
||||
@@ -4762,7 +4889,7 @@ fn install_antigravity_cli_writes_hook_and_updates_hooks_json() {
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn antigravity_cli_v2_install_is_outdated_until_reinstalled() {
|
||||
fn antigravity_cli_v3_install_is_outdated_until_reinstalled() {
|
||||
let _lock = integration_env_lock();
|
||||
let base = unique_base();
|
||||
let agy_dir = base.join(".gemini").join("config");
|
||||
@@ -4771,7 +4898,7 @@ fn antigravity_cli_v2_install_is_outdated_until_reinstalled() {
|
||||
fs::write(
|
||||
hook_dir.join(ANTIGRAVITY_CLI_HOOK_INSTALL_NAME),
|
||||
ANTIGRAVITY_CLI_HOOK_ASSET
|
||||
.replace("HERDR_INTEGRATION_VERSION=3", "HERDR_INTEGRATION_VERSION=2"),
|
||||
.replace("HERDR_INTEGRATION_VERSION=4", "HERDR_INTEGRATION_VERSION=3"),
|
||||
)
|
||||
.unwrap();
|
||||
std::env::set_var(ANTIGRAVITY_CLI_CONFIG_DIR_ENV_VAR, &agy_dir);
|
||||
@@ -4784,8 +4911,8 @@ fn antigravity_cli_v2_install_is_outdated_until_reinstalled() {
|
||||
};
|
||||
let outdated = status();
|
||||
assert_eq!(outdated.state, IntegrationStatusKind::Outdated);
|
||||
assert_eq!(outdated.installed_version, Some(2));
|
||||
assert_eq!(outdated.expected_version, 3);
|
||||
assert_eq!(outdated.installed_version, Some(3));
|
||||
assert_eq!(outdated.expected_version, 4);
|
||||
|
||||
install_antigravity_cli().unwrap();
|
||||
assert_eq!(status().state, IntegrationStatusKind::Current);
|
||||
|
||||
Reference in New Issue
Block a user