aboutsummaryrefslogtreecommitdiff
path: root/src/integration
diff options
context:
space:
mode:
authorOgulcan Celik <ogulcancelik@gmail.com>2026-07-24 02:49:33 +0300
committerOgulcan Celik <ogulcancelik@gmail.com>2026-07-24 02:50:58 +0300
commite608a75159fc5cca70485d83ffb8ee681c9da385 (patch)
tree5ffdec62d0bee9a798418d6414ecbddae7ff8ae5 /src/integration
parent169a4fd90c20a00aec77d684ecec98b9f0f996cc (diff)
fix: make known-agent exit process-owned
refs #1792 refs #1648
Diffstat (limited to 'src/integration')
-rw-r--r--src/integration/assets/hermes/__init__.py17
-rw-r--r--src/integration/assets/kilo/herdr-agent-state.js7
-rw-r--r--src/integration/assets/kimi/herdr-agent-state.ps14
-rw-r--r--src/integration/assets/kimi/herdr-agent-state.sh3
-rw-r--r--src/integration/assets/mastracode/herdr-agent-state.sh12
-rw-r--r--src/integration/assets/omp/herdr-agent-state.ts35
-rw-r--r--src/integration/assets/pi/herdr-agent-state.ts34
-rw-r--r--src/integration/mod.rs19
-rw-r--r--src/integration/targets.rs17
-rw-r--r--src/integration/tests.rs117
10 files changed, 132 insertions, 133 deletions
diff --git a/src/integration/assets/hermes/__init__.py b/src/integration/assets/hermes/__init__.py
index e6f6971d..5474e5e3 100644
--- a/src/integration/assets/hermes/__init__.py
+++ b/src/integration/assets/hermes/__init__.py
@@ -1,7 +1,7 @@
"""Hermes plugin installed by Herdr to report agent lifecycle state."""
# HERDR_INTEGRATION_ID=hermes
-# HERDR_INTEGRATION_VERSION=3
+# HERDR_INTEGRATION_VERSION=4
from __future__ import annotations
@@ -71,6 +71,19 @@ def _report(state: str, **kwargs) -> None:
_send("pane.report_agent", params)
+def _session_started(**kwargs) -> None:
+ session_id = _session_id(kwargs)
+ if session_id:
+ _send(
+ "pane.report_agent_session",
+ {
+ "agent_session_id": session_id,
+ "session_start_source": "startup",
+ },
+ )
+ _idle(**kwargs)
+
+
def _working(**kwargs) -> None:
_report("working", **kwargs)
@@ -84,7 +97,7 @@ def _idle(**kwargs) -> None:
def register(ctx):
- ctx.register_hook("on_session_start", _idle)
+ ctx.register_hook("on_session_start", _session_started)
ctx.register_hook("pre_llm_call", _working)
ctx.register_hook("pre_api_request", _working)
ctx.register_hook("pre_tool_call", _working)
diff --git a/src/integration/assets/kilo/herdr-agent-state.js b/src/integration/assets/kilo/herdr-agent-state.js
index e8600ab1..8071670c 100644
--- a/src/integration/assets/kilo/herdr-agent-state.js
+++ b/src/integration/assets/kilo/herdr-agent-state.js
@@ -2,7 +2,7 @@
// managed by herdr; reinstalling or updating the integration overwrites this file.
// add custom hooks/plugins beside this file instead of editing it.
// HERDR_INTEGRATION_ID=kilo
-// HERDR_INTEGRATION_VERSION=3
+// HERDR_INTEGRATION_VERSION=4
import net from "node:net";
@@ -88,7 +88,10 @@ function reportSession(sessionID) {
if (!sessionID) {
return Promise.resolve();
}
- return request("pane.report_agent_session", { agent_session_id: sessionID });
+ return request("pane.report_agent_session", {
+ agent_session_id: sessionID,
+ session_start_source: "startup",
+ });
}
function reportState(state, sessionID) {
diff --git a/src/integration/assets/kimi/herdr-agent-state.ps1 b/src/integration/assets/kimi/herdr-agent-state.ps1
index 16c9eb4d..c298dda4 100644
--- a/src/integration/assets/kimi/herdr-agent-state.ps1
+++ b/src/integration/assets/kimi/herdr-agent-state.ps1
@@ -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=5
+# HERDR_INTEGRATION_VERSION=6
param([string]$Action = "")
@@ -23,7 +23,7 @@ $sessionId = if ($null -ne $payload -and -not [string]::IsNullOrWhiteSpace($payl
try {
if ($Action -eq "session") {
if ([string]::IsNullOrWhiteSpace($sessionId)) { exit 0 }
- & herdr pane report-agent-session $env:HERDR_PANE_ID --source herdr:kimi --agent kimi --agent-session-id $sessionId --seq $seq 2>$null | Out-Null
+ & herdr pane report-agent-session $env:HERDR_PANE_ID --source herdr:kimi --agent kimi --agent-session-id $sessionId --session-start-source startup --seq $seq 2>$null | Out-Null
} else {
if ([string]::IsNullOrWhiteSpace($sessionId)) {
& herdr pane report-agent $env:HERDR_PANE_ID --source herdr:kimi --agent kimi --state $Action --seq $seq 2>$null | Out-Null
diff --git a/src/integration/assets/kimi/herdr-agent-state.sh b/src/integration/assets/kimi/herdr-agent-state.sh
index 7dac1b49..e919300f 100644
--- a/src/integration/assets/kimi/herdr-agent-state.sh
+++ b/src/integration/assets/kimi/herdr-agent-state.sh
@@ -1,7 +1,7 @@
#!/bin/sh
# managed by herdr; reinstalling the integration replaces this file.
# HERDR_INTEGRATION_ID=kimi
-# HERDR_INTEGRATION_VERSION=5
+# HERDR_INTEGRATION_VERSION=6
action="${1:-}"
case "$action" in
@@ -42,6 +42,7 @@ if action == "session":
if session_id is None:
raise SystemExit(0)
method = "pane.report_agent_session"
+ params["session_start_source"] = "startup"
else:
method = "pane.report_agent"
params["state"] = action
diff --git a/src/integration/assets/mastracode/herdr-agent-state.sh b/src/integration/assets/mastracode/herdr-agent-state.sh
index b8e0a26a..39a8461b 100644
--- a/src/integration/assets/mastracode/herdr-agent-state.sh
+++ b/src/integration/assets/mastracode/herdr-agent-state.sh
@@ -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=1
+# HERDR_INTEGRATION_VERSION=2
set -eu
@@ -13,7 +13,7 @@ trap 'rm -f "$hook_input_file"' EXIT HUP INT TERM
cat >"$hook_input_file" 2>/dev/null || true
case "$action" in
- working|idle|blocked|release) ;;
+ session|working|idle|blocked) ;;
*) exit 0 ;;
esac
@@ -55,14 +55,18 @@ if isinstance(session_id, str) and session_id:
agent_session_id = session_id
else:
agent_session_id = None
-if action == "release":
+if action == "session":
+ if not agent_session_id:
+ raise SystemExit(0)
request = {
"id": request_id,
- "method": "pane.release_agent",
+ "method": "pane.report_agent_session",
"params": {
"pane_id": pane_id,
"source": source,
"agent": "mastracode",
+ "agent_session_id": agent_session_id,
+ "session_start_source": "startup",
"seq": report_seq,
},
}
diff --git a/src/integration/assets/omp/herdr-agent-state.ts b/src/integration/assets/omp/herdr-agent-state.ts
index 350cdea7..b3f88c93 100644
--- a/src/integration/assets/omp/herdr-agent-state.ts
+++ b/src/integration/assets/omp/herdr-agent-state.ts
@@ -2,7 +2,7 @@
// managed by herdr; reinstalling or updating the integration overwrites this file.
// add custom hooks/plugins beside this file instead of editing it.
// HERDR_INTEGRATION_ID=omp
-// HERDR_INTEGRATION_VERSION=6
+// HERDR_INTEGRATION_VERSION=7
// @ts-nocheck
import net from "node:net";
@@ -168,29 +168,6 @@ function sendState(state: AgentState, message?: string, seq = nextReportSeq()):
});
}
-function releaseAgent(): Promise<void> {
- return sendRequest({
- id: `${source}:release:${Date.now()}:${Math.random().toString(36).slice(2)}`,
- method: "pane.release_agent",
- params: {
- pane_id: paneId,
- source,
- agent: "omp",
- seq: nextReportSeq(),
- },
- });
-}
-
-function shouldReleaseOnSessionShutdown(event: any): boolean {
- // OMP tears down and rebinds extension runtimes for internal lifecycle actions
- // such as /reload, /new, /resume, and /fork. Those do not mean the pane's
- // agent process has exited, and releasing hook authority there can suppress
- // legitimate reports from the replacement runtime. Only a user/process quit
- // should release Herdr's full-lifecycle authority.
- const reason = event?.reason;
- return reason === "quit";
-}
-
let sendInFlight = false;
let queuedState: QueuedState | undefined;
@@ -470,13 +447,9 @@ export default function (pi) {
scheduleIdle();
});
- pi.on("session_shutdown", async (event) => {
- if (!rootSession) {
- return;
- }
- clearPendingTimers();
- if (shouldReleaseOnSessionShutdown(event)) {
- await releaseAgent();
+ pi.on("session_shutdown", () => {
+ if (rootSession) {
+ clearPendingTimers();
}
});
}
diff --git a/src/integration/assets/pi/herdr-agent-state.ts b/src/integration/assets/pi/herdr-agent-state.ts
index caa50ed9..2e79c7c6 100644
--- a/src/integration/assets/pi/herdr-agent-state.ts
+++ b/src/integration/assets/pi/herdr-agent-state.ts
@@ -2,7 +2,7 @@
// managed by herdr; reinstalling or updating the integration overwrites this file.
// add custom hooks/plugins beside this file instead of editing it.
// HERDR_INTEGRATION_ID=pi
-// HERDR_INTEGRATION_VERSION=6
+// HERDR_INTEGRATION_VERSION=7
// @ts-nocheck
import net from "node:net";
@@ -142,29 +142,6 @@ function sendState(state: AgentState, message?: string, seq = nextReportSeq()):
});
}
-function releaseAgent(): Promise<void> {
- return sendRequest({
- id: `${source}:release:${Date.now()}:${Math.random().toString(36).slice(2)}`,
- method: "pane.release_agent",
- params: {
- pane_id: paneId,
- source,
- agent: "pi",
- seq: nextReportSeq(),
- },
- });
-}
-
-function shouldReleaseOnSessionShutdown(event: any): boolean {
- // Pi tears down and rebinds extension runtimes for internal lifecycle actions
- // such as /reload, /new, /resume, and /fork. Those do not mean the pane's
- // agent process has exited, and releasing hook authority there can suppress
- // legitimate reports from the replacement runtime. Only a user/process quit
- // should release Herdr's full-lifecycle authority.
- const reason = event?.reason;
- return reason === "quit";
-}
-
let sendInFlight = false;
let queuedState: QueuedState | undefined;
@@ -275,13 +252,4 @@ export default function (pi) {
agentActive = false;
publishState();
});
-
- pi.on("session_shutdown", async (event) => {
- if (!rootSession) {
- return;
- }
- if (shouldReleaseOnSessionShutdown(event)) {
- await releaseAgent();
- }
- });
}
diff --git a/src/integration/mod.rs b/src/integration/mod.rs
index 73c23fc6..d7bd8b3f 100644
--- a/src/integration/mod.rs
+++ b/src/integration/mod.rs
@@ -22,10 +22,10 @@ pub(crate) use types::{IntegrationRecommendation, IntegrationStatus, Integration
const PI_EXTENSION_INSTALL_NAME: &str = "herdr-agent-state.ts";
const PI_EXTENSION_ASSET: &str = include_str!("assets/pi/herdr-agent-state.ts");
-const PI_INTEGRATION_VERSION: u32 = 6;
+const PI_INTEGRATION_VERSION: u32 = 7;
const OMP_EXTENSION_INSTALL_NAME: &str = "herdr-omp-agent-state.ts";
const OMP_EXTENSION_ASSET: &str = include_str!("assets/omp/herdr-agent-state.ts");
-const OMP_INTEGRATION_VERSION: u32 = 6;
+const OMP_INTEGRATION_VERSION: u32 = 7;
const CLAUDE_HOOK_INSTALL_NAME: &str = if cfg!(windows) {
"herdr-agent-state.ps1"
} else {
@@ -58,7 +58,7 @@ const KIMI_HOOK_ASSET: &str = if cfg!(windows) {
} else {
include_str!("assets/kimi/herdr-agent-state.sh")
};
-const KIMI_INTEGRATION_VERSION: u32 = 5;
+const KIMI_INTEGRATION_VERSION: u32 = 6;
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";
@@ -160,13 +160,13 @@ const OPENCODE_PLUGIN_ASSET: &str = include_str!("assets/opencode/herdr-agent-st
const OPENCODE_INTEGRATION_VERSION: u32 = 9;
const KILO_PLUGIN_INSTALL_NAME: &str = "herdr-agent-state.js";
const KILO_PLUGIN_ASSET: &str = include_str!("assets/kilo/herdr-agent-state.js");
-const KILO_INTEGRATION_VERSION: u32 = 3;
+const KILO_INTEGRATION_VERSION: u32 = 4;
const HERMES_PLUGIN_INSTALL_NAME: &str = "herdr-agent-state";
const HERMES_PLUGIN_MANIFEST_INSTALL_NAME: &str = "plugin.yaml";
const HERMES_PLUGIN_INIT_INSTALL_NAME: &str = "__init__.py";
const HERMES_PLUGIN_MANIFEST_ASSET: &str = include_str!("assets/hermes/plugin.yaml");
const HERMES_PLUGIN_INIT_ASSET: &str = include_str!("assets/hermes/__init__.py");
-const HERMES_INTEGRATION_VERSION: u32 = 3;
+const HERMES_INTEGRATION_VERSION: u32 = 4;
const QODERCLI_HOOK_INSTALL_NAME: &str = if cfg!(windows) {
"herdr-agent-state.ps1"
} else {
@@ -198,10 +198,12 @@ const CURSOR_HOOK_ASSET: &str = include_str!("assets/cursor/herdr-agent-state.sh
const CURSOR_INTEGRATION_VERSION: u32 = 1;
const MASTRACODE_HOOK_INSTALL_NAME: &str = "herdr-agent-state.sh";
const MASTRACODE_HOOK_ASSET: &str = include_str!("assets/mastracode/herdr-agent-state.sh");
-const MASTRACODE_INTEGRATION_VERSION: u32 = 1;
+const MASTRACODE_INTEGRATION_VERSION: u32 = 2;
const MASTRACODE_HOOK_TIMEOUT_MS: u64 = 10_000;
-const MASTRACODE_HOOK_EVENTS: [(&str, &str); 12] = [
- ("SessionStart", "idle"),
+const MASTRACODE_REMOVED_HOOK_EVENTS: [(&str, &str); 2] =
+ [("SessionStart", "idle"), ("SessionEnd", "release")];
+const MASTRACODE_HOOK_EVENTS: [(&str, &str); 11] = [
+ ("SessionStart", "session"),
("UserPromptSubmit", "working"),
("AgentStart", "working"),
("PreToolUse", "working"),
@@ -212,7 +214,6 @@ const MASTRACODE_HOOK_EVENTS: [(&str, &str); 12] = [
("Interrupt", "idle"),
("AgentEnd", "idle"),
("Stop", "idle"),
- ("SessionEnd", "release"),
];
const INTEGRATION_VERSION_MARKER: &str = "HERDR_INTEGRATION_VERSION=";
diff --git a/src/integration/targets.rs b/src/integration/targets.rs
index 5d5e730c..488b7032 100644
--- a/src/integration/targets.rs
+++ b/src/integration/targets.rs
@@ -39,10 +39,11 @@ use super::{
HERMES_PLUGIN_INIT_INSTALL_NAME, HERMES_PLUGIN_MANIFEST_ASSET,
HERMES_PLUGIN_MANIFEST_INSTALL_NAME, KILO_PLUGIN_ASSET, KILO_PLUGIN_INSTALL_NAME,
KIMI_HOOK_ASSET, KIMI_HOOK_INSTALL_NAME, MASTRACODE_HOOK_ASSET, MASTRACODE_HOOK_EVENTS,
- MASTRACODE_HOOK_INSTALL_NAME, MASTRACODE_HOOK_TIMEOUT_MS, OMP_EXTENSION_ASSET,
- OMP_EXTENSION_INSTALL_NAME, OPENCODE_PLUGIN_ASSET, OPENCODE_PLUGIN_INSTALL_NAME,
- PI_EXTENSION_ASSET, PI_EXTENSION_INSTALL_NAME, QODERCLI_HOOK_ASSET, QODERCLI_HOOK_EVENTS,
- QODERCLI_HOOK_INSTALL_NAME, QODERCLI_REMOVED_LIFECYCLE_HOOK_EVENTS,
+ MASTRACODE_HOOK_INSTALL_NAME, MASTRACODE_HOOK_TIMEOUT_MS, MASTRACODE_REMOVED_HOOK_EVENTS,
+ OMP_EXTENSION_ASSET, OMP_EXTENSION_INSTALL_NAME, OPENCODE_PLUGIN_ASSET,
+ OPENCODE_PLUGIN_INSTALL_NAME, PI_EXTENSION_ASSET, PI_EXTENSION_INSTALL_NAME,
+ QODERCLI_HOOK_ASSET, QODERCLI_HOOK_EVENTS, QODERCLI_HOOK_INSTALL_NAME,
+ QODERCLI_REMOVED_LIFECYCLE_HOOK_EVENTS,
};
fn ensure_extension_dir(dir: &Path, agent: &str) -> io::Result<()> {
@@ -1133,6 +1134,9 @@ pub(crate) fn install_mastracode() -> io::Result<MastracodeInstallPaths> {
})?;
let quoted_hook_path = shell_single_quote(&hook_path.display().to_string());
+ for (event, action) in MASTRACODE_REMOVED_HOOK_EVENTS {
+ remove_flat_command_hook(hooks, event, &format!("bash {quoted_hook_path} {action}"))?;
+ }
for (event, action) in MASTRACODE_HOOK_EVENTS {
ensure_flat_command_hook(
hooks,
@@ -1171,7 +1175,10 @@ pub(crate) fn uninstall_mastracode() -> io::Result<MastracodeUninstallResult> {
})?;
let quoted_hook_path = shell_single_quote(&hook_path.display().to_string());
- for (event, action) in MASTRACODE_HOOK_EVENTS {
+ for (event, action) in MASTRACODE_HOOK_EVENTS
+ .into_iter()
+ .chain(MASTRACODE_REMOVED_HOOK_EVENTS)
+ {
updated_hooks |= remove_flat_command_hook(
hooks,
event,
diff --git a/src/integration/tests.rs b/src/integration/tests.rs
index 78b728f9..076a22b6 100644
--- a/src/integration/tests.rs
+++ b/src/integration/tests.rs
@@ -2667,8 +2667,7 @@ fn bundled_integration_assets_report_session_refs() {
assert!(PI_EXTENSION_ASSET.contains("pane.report_agent\""));
assert!(PI_EXTENSION_ASSET.contains("pi.on(\"agent_start\""));
assert!(PI_EXTENSION_ASSET.contains("pi.on(\"agent_settled\""));
- assert!(PI_EXTENSION_ASSET.contains("pane.release_agent"));
- assert!(PI_EXTENSION_ASSET.contains("pi.on(\"session_shutdown\""));
+ assert!(!PI_EXTENSION_ASSET.contains("pi.on(\"session_shutdown\""));
assert!(OMP_EXTENSION_ASSET.contains("agent_session_path"));
assert!(OMP_EXTENSION_ASSET.contains("agent_session_id"));
assert!(OMP_EXTENSION_ASSET.contains("ctx?.hasUI !== true"));
@@ -2676,7 +2675,6 @@ fn bundled_integration_assets_report_session_refs() {
assert!(OMP_EXTENSION_ASSET.contains("pane.report_agent\""));
assert!(OMP_EXTENSION_ASSET.contains("pi.on(\"agent_start\""));
assert!(OMP_EXTENSION_ASSET.contains("pi.on(\"agent_end\""));
- assert!(OMP_EXTENSION_ASSET.contains("pane.release_agent"));
assert!(OMP_EXTENSION_ASSET.contains("pi.on(\"session_shutdown\""));
assert!(
CLAUDE_HOOK_ASSET.contains("agent_session_id")
@@ -2718,6 +2716,7 @@ fn bundled_integration_assets_report_session_refs() {
assert!(KIMI_HOOK_ASSET.contains("source\": \"herdr:kimi"));
assert!(KIMI_HOOK_ASSET.contains("agent_session_id"));
assert!(KIMI_HOOK_ASSET.contains("method = \"pane.report_agent_session\""));
+ assert!(KIMI_HOOK_ASSET.contains("params[\"session_start_source\"] = \"startup\""));
assert!(KIMI_HOOK_ASSET.contains("method = \"pane.report_agent\""));
assert!(KIMI_HOOK_ASSET.contains("params[\"state\"] = action"));
assert!(!KIMI_HOOK_ASSET.contains("pane.release_agent"));
@@ -2743,10 +2742,13 @@ fn bundled_integration_assets_report_session_refs() {
assert!(KILO_PLUGIN_ASSET.contains("SOURCE = \"herdr:kilo\""));
assert!(KILO_PLUGIN_ASSET.contains("AGENT = \"kilo\""));
assert!(KILO_PLUGIN_ASSET.contains("pane.report_agent_session"));
+ assert!(KILO_PLUGIN_ASSET.contains("session_start_source: \"startup\""));
assert!(KILO_PLUGIN_ASSET.contains("reportState"));
assert!(!KILO_PLUGIN_ASSET.contains("pane.release_agent"));
assert!(HERMES_PLUGIN_INIT_ASSET.contains("session_id = _session_id(kwargs)"));
assert!(HERMES_PLUGIN_INIT_ASSET.contains("agent_session_id"));
+ assert!(HERMES_PLUGIN_INIT_ASSET.contains("pane.report_agent_session\","));
+ assert!(HERMES_PLUGIN_INIT_ASSET.contains("\"session_start_source\": \"startup\""));
assert!(HERMES_PLUGIN_INIT_ASSET.contains("pane.report_agent\","));
assert!(HERMES_PLUGIN_INIT_ASSET.contains("on_session_end"));
assert!(!HERMES_PLUGIN_INIT_ASSET.contains("on_session_finalize"));
@@ -2768,33 +2770,30 @@ fn bundled_integration_assets_report_session_refs() {
assert!(!CURSOR_HOOK_ASSET.contains("\"state\":"));
assert!(!CURSOR_HOOK_ASSET.contains("pane.release_agent"));
assert!(MASTRACODE_HOOK_ASSET.contains("HERDR_INTEGRATION_ID=mastracode"));
- assert!(MASTRACODE_HOOK_ASSET.contains("HERDR_INTEGRATION_VERSION=1"));
+ assert!(MASTRACODE_HOOK_ASSET.contains("HERDR_INTEGRATION_VERSION=2"));
assert!(MASTRACODE_HOOK_ASSET.contains("session_id"));
assert!(!MASTRACODE_HOOK_ASSET.contains("run_id"));
assert!(MASTRACODE_HOOK_ASSET.contains("agent_session_id"));
+ assert!(MASTRACODE_HOOK_ASSET.contains("pane.report_agent_session"));
+ assert!(MASTRACODE_HOOK_ASSET.contains("session_start_source"));
assert!(MASTRACODE_HOOK_ASSET.contains("pane.report_agent"));
- assert!(MASTRACODE_HOOK_ASSET.contains("pane.release_agent"));
}
#[test]
-fn pi_extension_releases_only_for_quit_session_shutdown() {
- let release_policy = PI_EXTENSION_ASSET
- .find("function shouldReleaseOnSessionShutdown")
- .expect("pi extension should centralize session shutdown release policy");
- let quit_check = PI_EXTENSION_ASSET
- .find("reason === \"quit\"")
- .expect("pi extension should release only for true quit shutdowns");
- let shutdown_handler = PI_EXTENSION_ASSET
- .find("pi.on(\"session_shutdown\", async (event)")
- .expect("pi extension should inspect the session_shutdown event");
- let guarded_release = PI_EXTENSION_ASSET[shutdown_handler..]
- .find("if (shouldReleaseOnSessionShutdown(event))")
- .expect("pi extension should guard releaseAgent by shutdown reason");
-
- assert!(release_policy < shutdown_handler);
- assert!(release_policy < quit_check);
- assert!(quit_check < shutdown_handler);
- assert!(guarded_release > 0);
+fn process_owned_integration_assets_do_not_report_release() {
+ for (name, asset) in [
+ ("pi", PI_EXTENSION_ASSET),
+ ("omp", OMP_EXTENSION_ASSET),
+ ("mastracode", MASTRACODE_HOOK_ASSET),
+ ("kimi", KIMI_HOOK_ASSET),
+ ("kilo", KILO_PLUGIN_ASSET),
+ ("hermes", HERMES_PLUGIN_INIT_ASSET),
+ ] {
+ assert!(
+ !asset.contains("pane.release_agent"),
+ "{name} process exit should own lifecycle release"
+ );
+ }
}
#[test]
@@ -2818,27 +2817,6 @@ fn pi_extension_refreshes_session_ref_before_agent_start_state() {
}
#[test]
-fn omp_extension_releases_only_for_quit_session_shutdown() {
- let release_policy = OMP_EXTENSION_ASSET
- .find("function shouldReleaseOnSessionShutdown")
- .expect("omp extension should centralize session shutdown release policy");
- let quit_check = OMP_EXTENSION_ASSET
- .find("reason === \"quit\"")
- .expect("omp extension should release only for true quit shutdowns");
- let shutdown_handler = OMP_EXTENSION_ASSET
- .find("pi.on(\"session_shutdown\", async (event)")
- .expect("omp extension should inspect the session_shutdown event");
- let guarded_release = OMP_EXTENSION_ASSET[shutdown_handler..]
- .find("if (shouldReleaseOnSessionShutdown(event))")
- .expect("omp extension should guard releaseAgent by shutdown reason");
-
- assert!(release_policy < shutdown_handler);
- assert!(release_policy < quit_check);
- assert!(quit_check < shutdown_handler);
- assert!(guarded_release > 0);
-}
-
-#[test]
fn omp_extension_refreshes_session_ref_before_agent_start_state() {
let agent_start = OMP_EXTENSION_ASSET
.find("pi.on(\"agent_start\", (_event, ctx)")
@@ -3354,6 +3332,57 @@ fn install_mastracode_writes_hook_and_updates_hooks_json() {
}
#[test]
+fn install_mastracode_removes_v1_lifecycle_hooks() {
+ let _lock = integration_env_lock();
+ let base = unique_base();
+ let original_home = std::env::var_os("HOME");
+ let mastracode_dir = base.join(".mastracode");
+ let hook_path = mastracode_dir
+ .join("hooks")
+ .join(MASTRACODE_HOOK_INSTALL_NAME);
+ fs::create_dir_all(&mastracode_dir).unwrap();
+ fs::write(
+ mastracode_dir.join("hooks.json"),
+ serde_json::to_string(&json!({
+ "SessionStart": [{
+ "type": "command",
+ "command": format!("bash '{}' idle", hook_path.display()),
+ "timeout": MASTRACODE_HOOK_TIMEOUT_MS
+ }],
+ "SessionEnd": [{
+ "type": "command",
+ "command": format!("bash '{}' release", hook_path.display()),
+ "timeout": MASTRACODE_HOOK_TIMEOUT_MS
+ }]
+ }))
+ .unwrap(),
+ )
+ .unwrap();
+ std::env::set_var("HOME", &base);
+
+ install_mastracode().unwrap();
+
+ let hooks_file: Value =
+ serde_json::from_str(&fs::read_to_string(mastracode_dir.join("hooks.json")).unwrap())
+ .unwrap();
+ let hooks = hooks_file.as_object().unwrap();
+ assert!(!hooks.contains_key("SessionEnd"));
+ let session_start = hooks["SessionStart"].as_array().unwrap();
+ assert_eq!(session_start.len(), 1);
+ assert!(session_start[0]["command"]
+ .as_str()
+ .unwrap()
+ .ends_with("session"));
+
+ if let Some(home) = original_home {
+ std::env::set_var("HOME", home);
+ } else {
+ std::env::remove_var("HOME");
+ }
+ let _ = fs::remove_dir_all(base);
+}
+
+#[test]
fn install_mastracode_is_idempotent_for_hook_entries() {
let _lock = integration_env_lock();
let base = unique_base();