diff options
Diffstat (limited to 'src/integration')
| -rw-r--r-- | src/integration/assets/hermes/__init__.py | 17 | ||||
| -rw-r--r-- | src/integration/assets/kilo/herdr-agent-state.js | 7 | ||||
| -rw-r--r-- | src/integration/assets/kimi/herdr-agent-state.ps1 | 4 | ||||
| -rw-r--r-- | src/integration/assets/kimi/herdr-agent-state.sh | 3 | ||||
| -rw-r--r-- | src/integration/assets/mastracode/herdr-agent-state.sh | 12 | ||||
| -rw-r--r-- | src/integration/assets/omp/herdr-agent-state.ts | 35 | ||||
| -rw-r--r-- | src/integration/assets/pi/herdr-agent-state.ts | 34 | ||||
| -rw-r--r-- | src/integration/mod.rs | 19 | ||||
| -rw-r--r-- | src/integration/targets.rs | 17 | ||||
| -rw-r--r-- | src/integration/tests.rs | 117 |
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(); |
