1	# self-clear.ps1 — one-shot self-clear + delayed self-wake.
2	#
3	# Order (per design):
4	#   1. Resolve this session's agent_id via `owl whoami` (context-independent; survives /clear).
5	#   2. Identify this session's terminal window by ANCESTOR PID: walk this process's
6	#      parent chain and find the visible top-level window owned by an ancestor pid
7	#      (the WindowsTerminal.exe hosting this tab). FOCUS it (the agent window is
8	#      usually NOT foreground when a tool runs), then VERIFY it actually came
9	#      foreground before doing anything — abort WITHOUT clearing if it didn't.
10	#      Capture its title bar text for the waker's safety check.
11	#   3. Type "/clear" + Enter  — QUEUED; fires only when this agent's turn ends.
12	#   4. Spawn a DETACHED waker that waits 200ms, re-focuses the captured HWND, and
13	#      — ONLY IF that HWND is foreground AND its title still matches the captured
14	#      title — TYPES the self-note as a fresh user prompt + Enter. Otherwise it ABORTS
15	#      the SendKeys (so the note never lands in the wrong window/tab).
16	#
17	# Why ancestor-pid + focus-first + HWND-identity guard: each Windows Terminal window
18	# is a SEPARATE WindowsTerminal.exe process here, so the ancestor pid uniquely
19	# identifies THIS session's window (Get-Process.MainWindowHandle would grab a
20	# process's first window, which is wrong when a host serves several). The window is
21	# generally not foreground while a tool runs, so we must focus it first and confirm
22	# the focus landed. The waker then guards the ~200ms gap by EXACT WINDOW IDENTITY:
23	# it types only if our specific HWND is foreground. A title-equality check can't be
24	# used — Claude Code's terminal title animates (spinner) and changes across /clear,
25	# so it would never match; the HWND check is the robust "right window" guard.
26	#
27	# Why detached + delayed: a blocking sleep here would hold the turn open, so /clear
28	# could never fire. The waker must outlive the turn. The short gap lets the turn end
29	# and /clear wipe the context first.
30	#
31	# Why TYPE the note (not `owl send`): after /clear the session sits IDLE awaiting user
32	# input. A spooled owl message cannot self-start a turn — it surfaces only during an
33	# already-active turn (PreToolUse hook) or in a live Monitor stream (whose buffer /clear
34	# wipes). So the prior "arrives as a waking poll message" design never woke the agent.
35	# A typed prompt + Enter deterministically starts a fresh turn AND is the re-seed.
36	$ErrorActionPreference = 'Stop'
37
38	# --- 1. Resolve agent_id ---
39	$owl = $env:OWL
40	if (-not $owl) { Write-Error 'OWL env not set — restart the session so the SessionStart hook runs.'; exit 1 }
41	$who = & $owl whoami 2>&1 | Out-String
42	if ($who -notmatch 'WHOAMI:([^\s]+)') {
43	  Write-Error "No perch for this session (whoami: $($who.Trim())). Run /spt:ready or /spt:live first — NOT clearing."
44	  exit 2
45	}
46	$id = $matches[1]
47
48	# --- 2. Identify (by ancestor pid), focus, and verify this session's terminal window ---
49	Add-Type @'
50	using System;
51	using System.Text;
52	using System.Runtime.InteropServices;
53	public class SC {
54	  [DllImport("user32.dll")] public static extern bool SetForegroundWindow(IntPtr h);
55	  [DllImport("user32.dll")] public static extern bool ShowWindow(IntPtr h, int n);
56	  [DllImport("user32.dll")] public static extern bool IsIconic(IntPtr h);
57	  [DllImport("user32.dll")] public static extern IntPtr GetForegroundWindow();
58	  [DllImport("user32.dll")] static extern bool EnumWindows(EnumWindowsProc cb, IntPtr l);
59	  [DllImport("user32.dll")] static extern uint GetWindowThreadProcessId(IntPtr h, out uint pid);
60	  [DllImport("user32.dll")] static extern bool IsWindowVisible(IntPtr h);
61	  [DllImport("user32.dll")] static extern IntPtr GetWindow(IntPtr h, uint cmd);
62	  [DllImport("user32.dll", CharSet=CharSet.Unicode)] static extern int GetWindowText(IntPtr h, StringBuilder s, int max);
63	  [DllImport("user32.dll")] static extern int GetWindowTextLength(IntPtr h);
64	  delegate bool EnumWindowsProc(IntPtr h, IntPtr l);
65	  const uint GW_OWNER = 4;
66	  // First visible, titled, un-owned (top-level) window belonging to `target` pid.
67	  public static IntPtr FindForPid(uint target) {
68	    IntPtr found = IntPtr.Zero;
69	    EnumWindows((h, l) => {
70	      uint pid; GetWindowThreadProcessId(h, out pid);
71	      if (pid == target && IsWindowVisible(h) && GetWindow(h, GW_OWNER) == IntPtr.Zero && GetWindowTextLength(h) > 0) {
72	        found = h; return false; // stop enumeration
73	      }
74	      return true;
75	    }, IntPtr.Zero);
76	    return found;
77	  }
78	  public static string TitleOf(IntPtr h) {
79	    int n = GetWindowTextLength(h);
80	    StringBuilder sb = new StringBuilder(n + 1);
81	    GetWindowText(h, sb, sb.Capacity);
82	    return sb.ToString();
83	  }
84	}
85	'@
86	# Walk the parent chain; the deepest ancestor pid that OWNS a visible top-level window
87	# is this session's terminal window (each WT window is its own process here).
88	$cur = $PID; $hwnd = [IntPtr]::Zero; $winpid = 0
89	for ($i = 0; $i -lt 8; $i++) {
90	  $ci = Get-CimInstance Win32_Process -Filter "ProcessId=$cur" -ErrorAction SilentlyContinue
91	  if (-not $ci) { break }
92	  $cand = [SC]::FindForPid([uint32]$cur)
93	  if ($cand -ne [IntPtr]::Zero) { $hwnd = $cand; $winpid = $cur; break }
94	  $cur = $ci.ParentProcessId
95	}
96	if ($hwnd -eq [IntPtr]::Zero) { Write-Error 'No ancestor-owned terminal window found in 8 hops — NOT clearing.'; exit 3 }
97
98	# FOCUS the target window first (it is usually NOT foreground while a tool runs).
99	$ws = New-Object -ComObject WScript.Shell
100	function Focus-Target {
101	  if ([SC]::IsIconic($hwnd)) { [SC]::ShowWindow($hwnd, 9) | Out-Null }  # 9 = SW_RESTORE
102	  [SC]::SetForegroundWindow($hwnd) | Out-Null
103	  try { $ws.AppActivate($winpid) | Out-Null } catch {}
104	}
105	Focus-Target
106	Start-Sleep -Milliseconds 200
107	if ([SC]::GetForegroundWindow() -ne $hwnd) { Focus-Target; Start-Sleep -Milliseconds 200 }
108	# VERIFY the focus actually landed — if not, do NOT type into the wrong window.
109	if ([SC]::GetForegroundWindow() -ne $hwnd) {
110	  Write-Error "Could not bring the agent's terminal window (pid=$winpid hwnd=$hwnd) to the foreground — NOT clearing."
111	  exit 4
112	}
113	$title = [SC]::TitleOf($hwnd)   # informational only (for the armed message)
114
115	# --- 3. Type /clear + Enter (queued; processes at turn-end) ---
116	$ws.SendKeys('/clear')
117	Start-Sleep -Milliseconds 150
118	$ws.SendKeys('{ENTER}')
119
120	# --- 4. Detached delayed waker (200ms) — re-foregrounds the captured HWND, then
121	#        TYPES the note ONLY IF that HWND is foreground AND its title is unchanged. ---
122	$hptr = $hwnd.ToInt64()
123	$note = 'Proceed with next steps'
124	$inner = @"
125	Start-Sleep -Milliseconds 200
126	Add-Type @'
127	using System;
128	using System.Runtime.InteropServices;
129	public class SCW {
130	  [DllImport("user32.dll")] public static extern bool SetForegroundWindow(IntPtr h);
131	  [DllImport("user32.dll")] public static extern bool ShowWindow(IntPtr h, int n);
132	  [DllImport("user32.dll")] public static extern bool IsIconic(IntPtr h);
133	  [DllImport("user32.dll")] public static extern IntPtr GetForegroundWindow();
134	}
135	'@
136	`$h = [IntPtr]$hptr
137	`$ws = New-Object -ComObject WScript.Shell
138	if ([SCW]::IsIconic(`$h)) { [SCW]::ShowWindow(`$h, 9) | Out-Null }
139	[SCW]::SetForegroundWindow(`$h) | Out-Null
140	try { `$ws.AppActivate($winpid) | Out-Null } catch {}
141	Start-Sleep -Milliseconds 120
142	# ABORT the SendKeys unless OUR EXACT window is the foreground window (right window only).
143	if ([SCW]::GetForegroundWindow() -ne `$h) { exit 0 }
144	`$ws.SendKeys('$note')
145	Start-Sleep -Milliseconds 150
146	`$ws.SendKeys('{ENTER}')
147	"@
148	$enc = [Convert]::ToBase64String([Text.Encoding]::Unicode.GetBytes($inner))
149	Start-Process -FilePath 'pwsh' -ArgumentList '-NoProfile','-NonInteractive','-EncodedCommand',$enc -WindowStyle Hidden | Out-Null
150
151	"SELF-CLEAR armed: id=$id winpid=$winpid hwnd=$hwnd title=`"$title`" — /clear queued; self-note typed in ~200ms (aborts if window/title changes)"
152

# self-clear.ps1 — one-shot self-clear + delayed self-wake.
#
# Order (per design):
#   1. Resolve this session's agent_id via `owl whoami` (context-independent; survives /clear).
#   2. Identify this session's terminal window by ANCESTOR PID: walk this process's
#      parent chain and find the visible top-level window owned by an ancestor pid
#      (the WindowsTerminal.exe hosting this tab). FOCUS it (the agent window is
#      usually NOT foreground when a tool runs), then VERIFY it actually came
#      foreground before doing anything — abort WITHOUT clearing if it didn't.
#      Capture its title bar text for the waker's safety check.
#   3. Type "/clear" + Enter  — QUEUED; fires only when this agent's turn ends.
#   4. Spawn a DETACHED waker that waits 200ms, re-focuses the captured HWND, and
#      — ONLY IF that HWND is foreground AND its title still matches the captured
#      title — TYPES the self-note as a fresh user prompt + Enter. Otherwise it ABORTS
#      the SendKeys (so the note never lands in the wrong window/tab).
#
# Why ancestor-pid + focus-first + HWND-identity guard: each Windows Terminal window
# is a SEPARATE WindowsTerminal.exe process here, so the ancestor pid uniquely
# identifies THIS session's window (Get-Process.MainWindowHandle would grab a
# process's first window, which is wrong when a host serves several). The window is
# generally not foreground while a tool runs, so we must focus it first and confirm
# the focus landed. The waker then guards the ~200ms gap by EXACT WINDOW IDENTITY:
# it types only if our specific HWND is foreground. A title-equality check can't be
# used — Claude Code's terminal title animates (spinner) and changes across /clear,
# so it would never match; the HWND check is the robust "right window" guard.
#
# Why detached + delayed: a blocking sleep here would hold the turn open, so /clear
# could never fire. The waker must outlive the turn. The short gap lets the turn end
# and /clear wipe the context first.
#
# Why TYPE the note (not `owl send`): after /clear the session sits IDLE awaiting user
# input. A spooled owl message cannot self-start a turn — it surfaces only during an
# already-active turn (PreToolUse hook) or in a live Monitor stream (whose buffer /clear
# wipes). So the prior "arrives as a waking poll message" design never woke the agent.
# A typed prompt + Enter deterministically starts a fresh turn AND is the re-seed.
$ErrorActionPreference = 'Stop'

# --- 1. Resolve agent_id ---
$owl = $env:OWL
if (-not $owl) { Write-Error 'OWL env not set — restart the session so the SessionStart hook runs.'; exit 1 }
$who = & $owl whoami 2>&1 | Out-String
if ($who -notmatch 'WHOAMI:([^\s]+)') {
  Write-Error "No perch for this session (whoami: $($who.Trim())). Run /spt:ready or /spt:live first — NOT clearing."
  exit 2
}
$id = $matches[1]

# --- 2. Identify (by ancestor pid), focus, and verify this session's terminal window ---
Add-Type @'
using System;
using System.Text;
using System.Runtime.InteropServices;
public class SC {
  [DllImport("user32.dll")] public static extern bool SetForegroundWindow(IntPtr h);
  [DllImport("user32.dll")] public static extern bool ShowWindow(IntPtr h, int n);
  [DllImport("user32.dll")] public static extern bool IsIconic(IntPtr h);
  [DllImport("user32.dll")] public static extern IntPtr GetForegroundWindow();
  [DllImport("user32.dll")] static extern bool EnumWindows(EnumWindowsProc cb, IntPtr l);
  [DllImport("user32.dll")] static extern uint GetWindowThreadProcessId(IntPtr h, out uint pid);
  [DllImport("user32.dll")] static extern bool IsWindowVisible(IntPtr h);
  [DllImport("user32.dll")] static extern IntPtr GetWindow(IntPtr h, uint cmd);
  [DllImport("user32.dll", CharSet=CharSet.Unicode)] static extern int GetWindowText(IntPtr h, StringBuilder s, int max);
  [DllImport("user32.dll")] static extern int GetWindowTextLength(IntPtr h);
  delegate bool EnumWindowsProc(IntPtr h, IntPtr l);
  const uint GW_OWNER = 4;
  // First visible, titled, un-owned (top-level) window belonging to `target` pid.
  public static IntPtr FindForPid(uint target) {
    IntPtr found = IntPtr.Zero;
    EnumWindows((h, l) => {
      uint pid; GetWindowThreadProcessId(h, out pid);
      if (pid == target && IsWindowVisible(h) && GetWindow(h, GW_OWNER) == IntPtr.Zero && GetWindowTextLength(h) > 0) {
        found = h; return false; // stop enumeration
      }
      return true;
    }, IntPtr.Zero);
    return found;
  }
  public static string TitleOf(IntPtr h) {
    int n = GetWindowTextLength(h);
    StringBuilder sb = new StringBuilder(n + 1);
    GetWindowText(h, sb, sb.Capacity);
    return sb.ToString();
  }
}
'@
# Walk the parent chain; the deepest ancestor pid that OWNS a visible top-level window
# is this session's terminal window (each WT window is its own process here).
$cur = $PID; $hwnd = [IntPtr]::Zero; $winpid = 0
for ($i = 0; $i -lt 8; $i++) {
  $ci = Get-CimInstance Win32_Process -Filter "ProcessId=$cur" -ErrorAction SilentlyContinue
  if (-not $ci) { break }
  $cand = [SC]::FindForPid([uint32]$cur)
  if ($cand -ne [IntPtr]::Zero) { $hwnd = $cand; $winpid = $cur; break }
  $cur = $ci.ParentProcessId
}
if ($hwnd -eq [IntPtr]::Zero) { Write-Error 'No ancestor-owned terminal window found in 8 hops — NOT clearing.'; exit 3 }

# FOCUS the target window first (it is usually NOT foreground while a tool runs).
$ws = New-Object -ComObject WScript.Shell
function Focus-Target {
  if ([SC]::IsIconic($hwnd)) { [SC]::ShowWindow($hwnd, 9) | Out-Null }  # 9 = SW_RESTORE
  [SC]::SetForegroundWindow($hwnd) | Out-Null
  try { $ws.AppActivate($winpid) | Out-Null } catch {}
}
Focus-Target
Start-Sleep -Milliseconds 200
if ([SC]::GetForegroundWindow() -ne $hwnd) { Focus-Target; Start-Sleep -Milliseconds 200 }
# VERIFY the focus actually landed — if not, do NOT type into the wrong window.
if ([SC]::GetForegroundWindow() -ne $hwnd) {
  Write-Error "Could not bring the agent's terminal window (pid=$winpid hwnd=$hwnd) to the foreground — NOT clearing."
  exit 4
}
$title = [SC]::TitleOf($hwnd)   # informational only (for the armed message)

# --- 3. Type /clear + Enter (queued; processes at turn-end) ---
$ws.SendKeys('/clear')
Start-Sleep -Milliseconds 150
$ws.SendKeys('{ENTER}')

# --- 4. Detached delayed waker (200ms) — re-foregrounds the captured HWND, then
#        TYPES the note ONLY IF that HWND is foreground AND its title is unchanged. ---
$hptr = $hwnd.ToInt64()
$note = 'Proceed with next steps'
$inner = @"
Start-Sleep -Milliseconds 200
Add-Type @'
using System;
using System.Runtime.InteropServices;
public class SCW {
  [DllImport("user32.dll")] public static extern bool SetForegroundWindow(IntPtr h);
  [DllImport("user32.dll")] public static extern bool ShowWindow(IntPtr h, int n);
  [DllImport("user32.dll")] public static extern bool IsIconic(IntPtr h);
  [DllImport("user32.dll")] public static extern IntPtr GetForegroundWindow();
}
'@
`$h = [IntPtr]$hptr
`$ws = New-Object -ComObject WScript.Shell
if ([SCW]::IsIconic(`$h)) { [SCW]::ShowWindow(`$h, 9) | Out-Null }
[SCW]::SetForegroundWindow(`$h) | Out-Null
try { `$ws.AppActivate($winpid) | Out-Null } catch {}
Start-Sleep -Milliseconds 120
# ABORT the SendKeys unless OUR EXACT window is the foreground window (right window only).
if ([SCW]::GetForegroundWindow() -ne `$h) { exit 0 }
`$ws.SendKeys('$note')
Start-Sleep -Milliseconds 150
`$ws.SendKeys('{ENTER}')
"@
$enc = [Convert]::ToBase64String([Text.Encoding]::Unicode.GetBytes($inner))
Start-Process -FilePath 'pwsh' -ArgumentList '-NoProfile','-NonInteractive','-EncodedCommand',$enc -WindowStyle Hidden | Out-Null

"SELF-CLEAR armed: id=$id winpid=$winpid hwnd=$hwnd title=`"$title`" — /clear queued; self-note typed in ~200ms (aborts if window/title changes)"


one session's agent just ran it while i had a different window focused, and again, the correct window was not focused at all, and it input into my active window as i was typing.


Found it. Line 103/140: `$ws.AppActivate($winpid)` only **transiently** flashes the window forward — a background process can't *hold* foreground. The guard at line 109 reads `GetForegroundWindow()==target` during that transient flash (passes), then focus snaps back to your window, and the `SendKeys` at 116 delivers to **your** now-foreground window. Classic TOCTOU race. `SendKeys` always hits whatever is foreground *at send time*, not at check time.

Real fix: reliable foreground steal via `AttachThreadInput` (synchronizes input queues so `SetForegroundWindow` actually holds), re-verify with **zero gap** before sending, send `/clear`+Enter as **one** call, and hard-abort if the steal fails. Drop `AppActivate` entirely.

Rewriting the C# + arm logic + step 3.


Edit

# --- 2. Identify (by ancestor pid), focus, and verify this session's terminal window ---
Add-Type @'
using System;
using System.Text;
using System.Runtime.InteropServices;
public class SC {
  [DllImport("user32.dll")] public static extern bool SetForegroundWindow(IntPtr h);
  [DllImport("user32.dll")] public static extern bool ShowWindow(IntPtr h, int n);
  [DllImport("user32.dll")] public static extern bool IsIconic(IntPtr h);
  [DllImport("user32.dll")] public static extern IntPtr GetForegroundWindow();
  [DllImport("user32.dll")] static extern bool EnumWindows(EnumWindowsProc cb, IntPtr l);
  [DllImport("user32.dll")] static extern uint GetWindowThreadProcessId(IntPtr h, out uint pid);
  [DllImport("user32.dll")] static extern bool IsWindowVisible(IntPtr h);
  [DllImport("user32.dll")] static extern IntPtr GetWindow(IntPtr h, uint cmd);
  [DllImport("user32.dll", CharSet=CharSet.Unicode)] static extern int GetWindowText(IntPtr h, StringBuilder s, int max);
  [DllImport("user32.dll")] static extern int GetWindowTextLength(IntPtr h);
  delegate bool EnumWindowsProc(IntPtr h, IntPtr l);
  const uint GW_OWNER = 4;
  // First visible, titled, un-owned (top-level) window belonging to `target` pid.
  public static IntPtr FindForPid(uint target) {
    IntPtr found = IntPtr.Zero;
    EnumWindows((h, l) => {
      uint pid; GetWindowThreadProcessId(h, out pid);
      if (pid == target && IsWindowVisible(h) && GetWindow(h, GW_OWNER) == IntPtr.Zero && GetWindowTextLength(h) > 0) {
        found = h; return false; // stop enumeration
      }
      return true;
    }, IntPtr.Zero);
    return found;
  }
  public static string TitleOf(IntPtr h) {
    int n = GetWindowTextLength(h);
    StringBuilder sb = new StringBuilder(n + 1);
    GetWindowText(h, sb, sb.Capacity);
    return sb.ToString();
  }
}
'@
# Walk the parent chain; the deepest ancestor pid that OWNS a visible top-level window
# is this session's terminal window (each WT window is its own process here).
$cur = $PID; $hwnd = [IntPtr]::Zero; $winpid = 0
for ($i = 0; $i -lt 8; $i++) {
  $ci = Get-CimInstance Win32_Process -Filter "ProcessId=$cur" -ErrorAction SilentlyContinue
  if (-not $ci) { break }
  $cand = [SC]::FindForPid([uint32]$cur)
  if ($cand -ne [IntPtr]::Zero) { $hwnd = $cand; $winpid = $cur; break }
  $cur = $ci.ParentProcessId
}
if ($hwnd -eq [IntPtr]::Zero) { Write-Error 'No ancestor-owned terminal window found in 8 hops — NOT clearing.'; exit 3 }

# FOCUS the target window first (it is usually NOT foreground while a tool runs).
$ws = New-Object -ComObject WScript.Shell
function Focus-Target {
  if ([SC]::IsIconic($hwnd)) { [SC]::ShowWindow($hwnd, 9) | Out-Null }  # 9 = SW_RESTORE
  [SC]::SetForegroundWindow($hwnd) | Out-Null
  try { $ws.AppActivate($winpid) | Out-Null } catch {}
}
Focus-Target
Start-Sleep -Milliseconds 200
if ([SC]::GetForegroundWindow() -ne $hwnd) { Focus-Target; Start-Sleep -Milliseconds 200 }
# VERIFY the focus actually landed — if not, do NOT type into the wrong window.
if ([SC]::GetForegroundWindow() -ne $hwnd) {
  Write-Error "Could not bring the agent's terminal window (pid=$winpid hwnd=$hwnd) to the foreground — NOT clearing."
  exit 4
}
$title = [SC]::TitleOf($hwnd)   # informational only (for the armed message)

# --- 3. Type /clear + Enter (queued; processes at turn-end) ---
$ws.SendKeys('/clear')
Start-Sleep -Milliseconds 150
$ws.SendKeys('{ENTER}')

# --- 2. Identify (by ancestor pid), reliably focus, and HARD-verify the window ---
Add-Type @'
using System;
using System.Text;
using System.Runtime.InteropServices;
public class SC {
  [DllImport("user32.dll")] public static extern bool SetForegroundWindow(IntPtr h);
  [DllImport("user32.dll")] public static extern bool ShowWindow(IntPtr h, int n);
  [DllImport("user32.dll")] public static extern bool BringWindowToTop(IntPtr h);
  [DllImport("user32.dll")] public static extern bool IsIconic(IntPtr h);
  [DllImport("user32.dll")] public static extern IntPtr GetForegroundWindow();
  [DllImport("user32.dll")] static extern bool EnumWindows(EnumWindowsProc cb, IntPtr l);
  [DllImport("user32.dll")] static extern uint GetWindowThreadProcessId(IntPtr h, out uint pid);
  [DllImport("user32.dll")] static extern bool IsWindowVisible(IntPtr h);
  [DllImport("user32.dll")] static extern IntPtr GetWindow(IntPtr h, uint cmd);
  [DllImport("user32.dll")] static extern int GetWindowTextLength(IntPtr h);
  [DllImport("user32.dll", CharSet=CharSet.Unicode)] static extern int GetWindowText(IntPtr h, StringBuilder s, int max);
  [DllImport("user32.dll")] static extern bool AttachThreadInput(uint a, uint b, bool f);
  [DllImport("kernel32.dll")] static extern uint GetCurrentThreadId();
  delegate bool EnumWindowsProc(IntPtr h, IntPtr l);
  const uint GW_OWNER = 4;
  // First visible, titled, un-owned (top-level) window belonging to `target` pid.
  public static IntPtr FindForPid(uint target) {
    IntPtr found = IntPtr.Zero;
    EnumWindows((h, l) => {
      uint pid; GetWindowThreadProcessId(h, out pid);
      if (pid == target && IsWindowVisible(h) && GetWindow(h, GW_OWNER) == IntPtr.Zero && GetWindowTextLength(h) > 0) {
        found = h; return false; // stop enumeration
      }
      return true;
    }, IntPtr.Zero);
    return found;
  }
  // RELIABLY bring `h` to the foreground. A background process can't normally steal
  // focus (SetForegroundWindow only flashes), so attach our input queue to the
  // current foreground thread first — that lets SetForegroundWindow actually hold.
  // Returns true ONLY if `h` is genuinely the foreground window afterwards.
  public static bool FocusAndConfirm(IntPtr h) {
    IntPtr fg = GetForegroundWindow();
    uint dummy; uint fgThread = GetWindowThreadProcessId(fg, out dummy);
    uint myThread = GetCurrentThreadId();
    bool attached = (fgThread != myThread) && AttachThreadInput(myThread, fgThread, true);
    if (IsIconic(h)) ShowWindow(h, 9); // SW_RESTORE
    BringWindowToTop(h);
    SetForegroundWindow(h);
    bool ok = (GetForegroundWindow() == h);
    if (attached) AttachThreadInput(myThread, fgThread, false);
    return ok;
  }
  public static string TitleOf(IntPtr h) {
    int n = GetWindowTextLength(h);
    StringBuilder sb = new StringBuilder(n + 1);
    GetWindowText(h, sb, sb.Capacity);
    return sb.ToString();
  }
}
'@
# Walk the parent chain; the deepest ancestor pid that OWNS a visible top-level window
# is this session's terminal window (each WT window is its own process here).
$cur = $PID; $hwnd = [IntPtr]::Zero; $winpid = 0
for ($i = 0; $i -lt 8; $i++) {
  $ci = Get-CimInstance Win32_Process -Filter "ProcessId=$cur" -ErrorAction SilentlyContinue
  if (-not $ci) { break }
  $cand = [SC]::FindForPid([uint32]$cur)
  if ($cand -ne [IntPtr]::Zero) { $hwnd = $cand; $winpid = $cur; break }
  $cur = $ci.ParentProcessId
}
if ($hwnd -eq [IntPtr]::Zero) { Write-Error 'No ancestor-owned terminal window found in 8 hops — NOT clearing.'; exit 3 }

# Reliably focus the target (two attempts). If it will not hold the foreground, ABORT —
# never SendKeys into whatever window the user happens to be in.
$ok = [SC]::FocusAndConfirm($hwnd)
if (-not $ok) { Start-Sleep -Milliseconds 150; $ok = [SC]::FocusAndConfirm($hwnd) }
if (-not $ok -or [SC]::GetForegroundWindow() -ne $hwnd) {
  Write-Error "Could not hold the agent's terminal window (pid=$winpid hwnd=$hwnd) in the foreground — NOT clearing (no keys sent)."
  exit 4
}
$title = [SC]::TitleOf($hwnd)   # informational only (for the armed message)

# --- 3. Type /clear + Enter (queued; processes at turn-end) ---
# FINAL zero-gap guard immediately before sending — abort if focus drifted.
if ([SC]::GetForegroundWindow() -ne $hwnd) { Write-Error 'Lost foreground before send — NOT clearing.'; exit 4 }
$ws = New-Object -ComObject WScript.Shell
$ws.SendKeys('/clear{ENTER}')


The file C:\Users\decid\.claude\skills\self-clear\scripts\self-clear.ps1 has been updated successfully. (file state is current in your context — no need to Read it back)

# --- 2. Identify (by ancestor pid), reliably focus, and HARD-verify the window ---
Add-Type @'
using System;
using System.Text;
using System.Runtime.InteropServices;
public class SC {
  [DllImport("user32.dll")] public static extern bool SetForegroundWindow(IntPtr h);
  [DllImport("user32.dll")] public static extern bool ShowWindow(IntPtr h, int n);
  [DllImport("user32.dll")] public static extern bool BringWindowToTop(IntPtr h);
  [DllImport("user32.dll")] public static extern bool IsIconic(IntPtr h);
  [DllImport("user32.dll")] public static extern IntPtr GetForegroundWindow();
  [DllImport("user32.dll")] static extern bool EnumWindows(EnumWindowsProc cb, IntPtr l);
  [DllImport("user32.dll")] static extern uint GetWindowThreadProcessId(IntPtr h, out uint pid);
  [DllImport("user32.dll")] static extern bool IsWindowVisible(IntPtr h);
  [DllImport("user32.dll")] static extern IntPtr GetWindow(IntPtr h, uint cmd);
  [DllImport("user32.dll")] static extern int GetWindowTextLength(IntPtr h);
  [DllImport("user32.dll", CharSet=CharSet.Unicode)] static extern int GetWindowText(IntPtr h, StringBuilder s, int max);
  [DllImport("user32.dll")] static extern bool AttachThreadInput(uint a, uint b, bool f);
  [DllImport("kernel32.dll")] static extern uint GetCurrentThreadId();
  delegate bool EnumWindowsProc(IntPtr h, IntPtr l);
  const uint GW_OWNER = 4;
  // First visible, titled, un-owned (top-level) window belonging to `target` pid.
  public static IntPtr FindForPid(uint target) {
    IntPtr found = IntPtr.Zero;
    EnumWindows((h, l) => {
      uint pid; GetWindowThreadProcessId(h, out pid);
      if (pid == target && IsWindowVisible(h) && GetWindow(h, GW_OWNER) == IntPtr.Zero && GetWindowTextLength(h) > 0) {
        found = h; return false; // stop enumeration
      }
      return true;
    }, IntPtr.Zero);
    return found;
  }
  // RELIABLY bring `h` to the foreground. A background process can't normally steal
  // focus (SetForegroundWindow only flashes), so attach our input queue to the
  // current foreground thread first — that lets SetForegroundWindow actually hold.
  // Returns true ONLY if `h` is genuinely the foreground window afterwards.
  public static bool FocusAndConfirm(IntPtr h) {
    IntPtr fg = GetForegroundWindow();
    uint dummy; uint fgThread = GetWindowThreadProcessId(fg, out dummy);
    uint myThread = GetCurrentThreadId();
    bool attached = (fgThread != myThread) && AttachThreadInput(myThread, fgThread, true);
    if (IsIconic(h)) ShowWindow(h, 9); // SW_RESTORE
    BringWindowToTop(h);
    SetForegroundWindow(h);
    bool ok = (GetForegroundWindow() == h);
    if (attached) AttachThreadInput(myThread, fgThread, false);
    return ok;
  }
  public static string TitleOf(IntPtr h) {
    int n = GetWindowTextLength(h);
    StringBuilder sb = new StringBuilder(n + 1);
    GetWindowText(h, sb, sb.Capacity);
    return sb.ToString();
  }
}
'@
# Walk the parent chain; the deepest ancestor pid that OWNS a visible top-level window
# is this session's terminal window (each WT window is its own process here).
$cur = $PID; $hwnd = [IntPtr]::Zero; $winpid = 0
for ($i = 0; $i -lt 8; $i++) {
  $ci = Get-CimInstance Win32_Process -Filter "ProcessId=$cur" -ErrorAction SilentlyContinue
  if (-not $ci) { break }
  $cand = [SC]::FindForPid([uint32]$cur)
  if ($cand -ne [IntPtr]::Zero) { $hwnd = $cand; $winpid = $cur; break }
  $cur = $ci.ParentProcessId
}
if ($hwnd -eq [IntPtr]::Zero) { Write-Error 'No ancestor-owned terminal window found in 8 hops — NOT clearing.'; exit 3 }

# Reliably focus the target (two attempts). If it will not hold the foreground, ABORT —
# never SendKeys into whatever window the user happens to be in.
$ok = [SC]::FocusAndConfirm($hwnd)
if (-not $ok) { Start-Sleep -Milliseconds 150; $ok = [SC]::FocusAndConfirm($hwnd) }
if (-not $ok -or [SC]::GetForegroundWindow() -ne $hwnd) {
  Write-Error "Could not hold the agent's terminal window (pid=$winpid hwnd=$hwnd) in the foreground — NOT clearing (no keys sent)."
  exit 4
}
$title = [SC]::TitleOf($hwnd)   # informational only (for the armed message)

# --- 3. Type /clear + Enter (queued; processes at turn-end) ---
# FINAL zero-gap guard immediately before sending — abort if focus drifted.
if ([SC]::GetForegroundWindow() -ne $hwnd) { Write-Error 'Lost foreground before send — NOT clearing.'; exit 4 }
$ws = New-Object -ComObject WScript.Shell
$ws.SendKeys('/clear{ENTER}')

# self-clear.ps1 — one-shot self-clear + delayed self-wake.
#
# Order (per design):
#   1. Resolve this session's agent_id via `owl whoami` (context-independent; survives /clear).
#   2. Identify this session's terminal window by ANCESTOR PID: walk this process's
#      parent chain and find the visible top-level window owned by an ancestor pid
#      (the WindowsTerminal.exe hosting this tab). FOCUS it (the agent window is
#      usually NOT foreground when a tool runs), then VERIFY it actually came
#      foreground before doing anything — abort WITHOUT clearing if it didn't.
#      Capture its title bar text for the waker's safety check.
#   3. Type "/clear" + Enter  — QUEUED; fires only when this agent's turn ends.
#   4. Spawn a DETACHED waker that waits 200ms, re-focuses the captured HWND, and
#      — ONLY IF that HWND is foreground AND its title still matches the captured
#      title — TYPES the self-note as a fresh user prompt + Enter. Otherwise it ABORTS
#      the SendKeys (so the note never lands in the wrong window/tab).
#
# Why ancestor-pid + focus-first + HWND-identity guard: each Windows Terminal window
# is a SEPARATE WindowsTerminal.exe process here, so the ancestor pid uniquely
# identifies THIS session's window (Get-Process.MainWindowHandle would grab a
# process's first window, which is wrong when a host serves several). The window is
# generally not foreground while a tool runs, so we must focus it first and confirm
# the focus landed. The waker then guards the ~200ms gap by EXACT WINDOW IDENTITY:
# it types only if our specific HWND is foreground. A title-equality check can't be
# used — Claude Code's terminal title animates (spinner) and changes across /clear,
# so it would never match; the HWND check is the robust "right window" guard.
#
# Why detached + delayed: a blocking sleep here would hold the turn open, so /clear
# could never fire. The waker must outlive the turn. The short gap lets the turn end
# and /clear wipe the context first.
#
# Why TYPE the note (not `owl send`): after /clear the session sits IDLE awaiting user
# input. A spooled owl message cannot self-start a turn — it surfaces only during an
# already-active turn (PreToolUse hook) or in a live Monitor stream (whose buffer /clear
# wipes). So the prior "arrives as a waking poll message" design never woke the agent.
# A typed prompt + Enter deterministically starts a fresh turn AND is the re-seed.
$ErrorActionPreference = 'Stop'

# --- 1. Resolve agent_id ---
$owl = $env:OWL
if (-not $owl) { Write-Error 'OWL env not set — restart the session so the SessionStart hook runs.'; exit 1 }
$who = & $owl whoami 2>&1 | Out-String
if ($who -notmatch 'WHOAMI:([^\s]+)') {
  Write-Error "No perch for this session (whoami: $($who.Trim())). Run /spt:ready or /spt:live first — NOT clearing."
  exit 2
}
$id = $matches[1]

# --- 2. Identify (by ancestor pid), focus, and verify this session's terminal window ---
Add-Type @'
using System;
using System.Text;
using System.Runtime.InteropServices;
public class SC {
  [DllImport("user32.dll")] public static extern bool SetForegroundWindow(IntPtr h);
  [DllImport("user32.dll")] public static extern bool ShowWindow(IntPtr h, int n);
  [DllImport("user32.dll")] public static extern bool IsIconic(IntPtr h);
  [DllImport("user32.dll")] public static extern IntPtr GetForegroundWindow();
  [DllImport("user32.dll")] static extern bool EnumWindows(EnumWindowsProc cb, IntPtr l);
  [DllImport("user32.dll")] static extern uint GetWindowThreadProcessId(IntPtr h, out uint pid);
  [DllImport("user32.dll")] static extern bool IsWindowVisible(IntPtr h);
  [DllImport("user32.dll")] static extern IntPtr GetWindow(IntPtr h, uint cmd);
  [DllImport("user32.dll", CharSet=CharSet.Unicode)] static extern int GetWindowText(IntPtr h, StringBuilder s, int max);
  [DllImport("user32.dll")] static extern int GetWindowTextLength(IntPtr h);
  delegate bool EnumWindowsProc(IntPtr h, IntPtr l);
  const uint GW_OWNER = 4;
  // First visible, titled, un-owned (top-level) window belonging to `target` pid.
  public static IntPtr FindForPid(uint target) {
    IntPtr found = IntPtr.Zero;
    EnumWindows((h, l) => {
      uint pid; GetWindowThreadProcessId(h, out pid);
      if (pid == target && IsWindowVisible(h) && GetWindow(h, GW_OWNER) == IntPtr.Zero && GetWindowTextLength(h) > 0) {
        found = h; return false; // stop enumeration
      }
      return true;
    }, IntPtr.Zero);
    return found;
  }
  public static string TitleOf(IntPtr h) {
    int n = GetWindowTextLength(h);
    StringBuilder sb = new StringBuilder(n + 1);
    GetWindowText(h, sb, sb.Capacity);
    return sb.ToString();
  }
}
'@
# Walk the parent chain; the deepest ancestor pid that OWNS a visible top-level window
# is this session's terminal window (each WT window is its own process here).
$cur = $PID; $hwnd = [IntPtr]::Zero; $winpid = 0
for ($i = 0; $i -lt 8; $i++) {
  $ci = Get-CimInstance Win32_Process -Filter "ProcessId=$cur" -ErrorAction SilentlyContinue
  if (-not $ci) { break }
  $cand = [SC]::FindForPid([uint32]$cur)
  if ($cand -ne [IntPtr]::Zero) { $hwnd = $cand; $winpid = $cur; break }
  $cur = $ci.ParentProcessId
}
if ($hwnd -eq [IntPtr]::Zero) { Write-Error 'No ancestor-owned terminal window found in 8 hops — NOT clearing.'; exit 3 }

# FOCUS the target window first (it is usually NOT foreground while a tool runs).
$ws = New-Object -ComObject WScript.Shell
function Focus-Target {
  if ([SC]::IsIconic($hwnd)) { [SC]::ShowWindow($hwnd, 9) | Out-Null }  # 9 = SW_RESTORE
  [SC]::SetForegroundWindow($hwnd) | Out-Null
  try { $ws.AppActivate($winpid) | Out-Null } catch {}
}
Focus-Target
Start-Sleep -Milliseconds 200
if ([SC]::GetForegroundWindow() -ne $hwnd) { Focus-Target; Start-Sleep -Milliseconds 200 }
# VERIFY the focus actually landed — if not, do NOT type into the wrong window.
if ([SC]::GetForegroundWindow() -ne $hwnd) {
  Write-Error "Could not bring the agent's terminal window (pid=$winpid hwnd=$hwnd) to the foreground — NOT clearing."
  exit 4
}
$title = [SC]::TitleOf($hwnd)   # informational only (for the armed message)

# --- 3. Type /clear + Enter (queued; processes at turn-end) ---
$ws.SendKeys('/clear')
Start-Sleep -Milliseconds 150
$ws.SendKeys('{ENTER}')

# --- 4. Detached delayed waker (200ms) — re-foregrounds the captured HWND, then
#        TYPES the note ONLY IF that HWND is foreground AND its title is unchanged. ---
$hptr = $hwnd.ToInt64()
$note = 'Proceed with next steps'
$inner = @"
Start-Sleep -Milliseconds 200
Add-Type @'
using System;
using System.Runtime.InteropServices;
public class SCW {
  [DllImport("user32.dll")] public static extern bool SetForegroundWindow(IntPtr h);
  [DllImport("user32.dll")] public static extern bool ShowWindow(IntPtr h, int n);
  [DllImport("user32.dll")] public static extern bool IsIconic(IntPtr h);
  [DllImport("user32.dll")] public static extern IntPtr GetForegroundWindow();
}
'@
`$h = [IntPtr]$hptr
`$ws = New-Object -ComObject WScript.Shell
if ([SCW]::IsIconic(`$h)) { [SCW]::ShowWindow(`$h, 9) | Out-Null }
[SCW]::SetForegroundWindow(`$h) | Out-Null
try { `$ws.AppActivate($winpid) | Out-Null } catch {}
Start-Sleep -Milliseconds 120
# ABORT the SendKeys unless OUR EXACT window is the foreground window (right window only).
if ([SCW]::GetForegroundWindow() -ne `$h) { exit 0 }
`$ws.SendKeys('$note')
Start-Sleep -Milliseconds 150
`$ws.SendKeys('{ENTER}')
"@
$enc = [Convert]::ToBase64String([Text.Encoding]::Unicode.GetBytes($inner))
Start-Process -FilePath 'pwsh' -ArgumentList '-NoProfile','-NonInteractive','-EncodedCommand',$enc -WindowStyle Hidden | Out-Null

"SELF-CLEAR armed: id=$id winpid=$winpid hwnd=$hwnd title=`"$title`" — /clear queued; self-note typed in ~200ms (aborts if window/title changes)"

-# --- 2. Identify (by ancestor pid), focus, and verify this session's terminal window ---

+# --- 2. Identify (by ancestor pid), reliably focus, and HARD-verify the window ---

+  [DllImport("user32.dll")] public static extern bool BringWindowToTop(IntPtr h);

-  [DllImport("user32.dll", CharSet=CharSet.Unicode)] static extern int GetWindowText(IntPtr h, StringBuilder s, int max);

+  [DllImport("user32.dll", CharSet=CharSet.Unicode)] static extern int GetWindowText(IntPtr h, StringBuilder s, int max);

+  [DllImport("user32.dll")] static extern bool AttachThreadInput(uint a, uint b, bool f);

+  [DllImport("kernel32.dll")] static extern uint GetCurrentThreadId();

+  // RELIABLY bring `h` to the foreground. A background process can't normally steal

+  // focus (SetForegroundWindow only flashes), so attach our input queue to the

+  // current foreground thread first — that lets SetForegroundWindow actually hold.

+  // Returns true ONLY if `h` is genuinely the foreground window afterwards.

+  public static bool FocusAndConfirm(IntPtr h) {

+    uint dummy; uint fgThread = GetWindowThreadProcessId(fg, out dummy);

+    uint myThread = GetCurrentThreadId();

+    bool attached = (fgThread != myThread) && AttachThreadInput(myThread, fgThread, true);

+    if (IsIconic(h)) ShowWindow(h, 9); // SW_RESTORE

+    bool ok = (GetForegroundWindow() == h);

+    if (attached) AttachThreadInput(myThread, fgThread, false);

-# FOCUS the target window first (it is usually NOT foreground while a tool runs).

-$ws = New-Object -ComObject WScript.Shell

-  if ([SC]::IsIconic($hwnd)) { [SC]::ShowWindow($hwnd, 9) | Out-Null }  # 9 = SW_RESTORE

-  [SC]::SetForegroundWindow($hwnd) | Out-Null

-  try { $ws.AppActivate($winpid) | Out-Null } catch {}

-if ([SC]::GetForegroundWindow() -ne $hwnd) { Focus-Target; Start-Sleep -Milliseconds 200 }

-# VERIFY the focus actually landed — if not, do NOT type into the wrong window.

-  Write-Error "Could not bring the agent's terminal window (pid=$winpid hwnd=$hwnd) to the foreground — NOT clearing."

+# Reliably focus the target (two attempts). If it will not hold the foreground, ABORT —

+# never SendKeys into whatever window the user happens to be in.

+if (-not $ok) { Start-Sleep -Milliseconds 150; $ok = [SC]::FocusAndConfirm($hwnd) }

+if (-not $ok -or [SC]::GetForegroundWindow() -ne $hwnd) {

+  Write-Error "Could not hold the agent's terminal window (pid=$winpid hwnd=$hwnd) in the foreground — NOT clearing (no keys sent)."

+# FINAL zero-gap guard immediately before sending — abort if focus drifted.

+if ([SC]::GetForegroundWindow() -ne $hwnd) { Write-Error 'Lost foreground before send — NOT clearing.'; exit 4 }

+$ws = New-Object -ComObject WScript.Shell


Now the waker — same reliable steal + zero-gap guard + single send.


$hptr = $hwnd.ToInt64()
$note = 'Proceed with next steps'
$inner = @"
Start-Sleep -Milliseconds 200
Add-Type @'
using System;
using System.Runtime.InteropServices;
public class SCW {
  [DllImport("user32.dll")] public static extern bool SetForegroundWindow(IntPtr h);
  [DllImport("user32.dll")] public static extern bool ShowWindow(IntPtr h, int n);
  [DllImport("user32.dll")] public static extern bool IsIconic(IntPtr h);
  [DllImport("user32.dll")] public static extern IntPtr GetForegroundWindow();
}
'@
`$h = [IntPtr]$hptr
`$ws = New-Object -ComObject WScript.Shell
if ([SCW]::IsIconic(`$h)) { [SCW]::ShowWindow(`$h, 9) | Out-Null }
[SCW]::SetForegroundWindow(`$h) | Out-Null
try { `$ws.AppActivate($winpid) | Out-Null } catch {}
Start-Sleep -Milliseconds 120
# ABORT the SendKeys unless OUR EXACT window is the foreground window (right window only).
if ([SCW]::GetForegroundWindow() -ne `$h) { exit 0 }
`$ws.SendKeys('$note')
Start-Sleep -Milliseconds 150
`$ws.SendKeys('{ENTER}')
"@

$hptr = $hwnd.ToInt64()
$note = 'Proceed with next steps'
$inner = @"
Start-Sleep -Milliseconds 200
Add-Type @'
using System;
using System.Runtime.InteropServices;
public class SCW {
  [DllImport("user32.dll")] public static extern bool SetForegroundWindow(IntPtr h);
  [DllImport("user32.dll")] public static extern bool ShowWindow(IntPtr h, int n);
  [DllImport("user32.dll")] public static extern bool BringWindowToTop(IntPtr h);
  [DllImport("user32.dll")] public static extern bool IsIconic(IntPtr h);
  [DllImport("user32.dll")] public static extern IntPtr GetForegroundWindow();
  [DllImport("user32.dll")] static extern uint GetWindowThreadProcessId(IntPtr h, out uint pid);
  [DllImport("user32.dll")] static extern bool AttachThreadInput(uint a, uint b, bool f);
  [DllImport("kernel32.dll")] static extern uint GetCurrentThreadId();
  public static bool FocusAndConfirm(IntPtr h) {
    IntPtr fg = GetForegroundWindow();
    uint dummy; uint fgThread = GetWindowThreadProcessId(fg, out dummy);
    uint myThread = GetCurrentThreadId();
    bool attached = (fgThread != myThread) && AttachThreadInput(myThread, fgThread, true);
    if (IsIconic(h)) ShowWindow(h, 9);
    BringWindowToTop(h);
    SetForegroundWindow(h);
    bool ok = (GetForegroundWindow() == h);
    if (attached) AttachThreadInput(myThread, fgThread, false);
    return ok;
  }
}
'@
`$h = [IntPtr]$hptr
# Reliably focus OUR window. ABORT (no keys) unless it genuinely holds the foreground.
if (-not [SCW]::FocusAndConfirm(`$h)) { exit 0 }
if ([SCW]::GetForegroundWindow() -ne `$h) { exit 0 }
`$ws = New-Object -ComObject WScript.Shell
`$ws.SendKeys('$note{ENTER}')
"@


$hptr = $hwnd.ToInt64()
$note = 'Proceed with next steps'
$inner = @"
Start-Sleep -Milliseconds 200
Add-Type @'
using System;
using System.Runtime.InteropServices;
public class SCW {
  [DllImport("user32.dll")] public static extern bool SetForegroundWindow(IntPtr h);
  [DllImport("user32.dll")] public static extern bool ShowWindow(IntPtr h, int n);
  [DllImport("user32.dll")] public static extern bool BringWindowToTop(IntPtr h);
  [DllImport("user32.dll")] public static extern bool IsIconic(IntPtr h);
  [DllImport("user32.dll")] public static extern IntPtr GetForegroundWindow();
  [DllImport("user32.dll")] static extern uint GetWindowThreadProcessId(IntPtr h, out uint pid);
  [DllImport("user32.dll")] static extern bool AttachThreadInput(uint a, uint b, bool f);
  [DllImport("kernel32.dll")] static extern uint GetCurrentThreadId();
  public static bool FocusAndConfirm(IntPtr h) {
    IntPtr fg = GetForegroundWindow();
    uint dummy; uint fgThread = GetWindowThreadProcessId(fg, out dummy);
    uint myThread = GetCurrentThreadId();
    bool attached = (fgThread != myThread) && AttachThreadInput(myThread, fgThread, true);
    if (IsIconic(h)) ShowWindow(h, 9);
    BringWindowToTop(h);
    SetForegroundWindow(h);
    bool ok = (GetForegroundWindow() == h);
    if (attached) AttachThreadInput(myThread, fgThread, false);
    return ok;
  }
}
'@
`$h = [IntPtr]$hptr
# Reliably focus OUR window. ABORT (no keys) unless it genuinely holds the foreground.
if (-not [SCW]::FocusAndConfirm(`$h)) { exit 0 }
if ([SCW]::GetForegroundWindow() -ne `$h) { exit 0 }
`$ws = New-Object -ComObject WScript.Shell
`$ws.SendKeys('$note{ENTER}')
"@

# self-clear.ps1 — one-shot self-clear + delayed self-wake.
#
# Order (per design):
#   1. Resolve this session's agent_id via `owl whoami` (context-independent; survives /clear).
#   2. Identify this session's terminal window by ANCESTOR PID: walk this process's
#      parent chain and find the visible top-level window owned by an ancestor pid
#      (the WindowsTerminal.exe hosting this tab). FOCUS it (the agent window is
#      usually NOT foreground when a tool runs), then VERIFY it actually came
#      foreground before doing anything — abort WITHOUT clearing if it didn't.
#      Capture its title bar text for the waker's safety check.
#   3. Type "/clear" + Enter  — QUEUED; fires only when this agent's turn ends.
#   4. Spawn a DETACHED waker that waits 200ms, re-focuses the captured HWND, and
#      — ONLY IF that HWND is foreground AND its title still matches the captured
#      title — TYPES the self-note as a fresh user prompt + Enter. Otherwise it ABORTS
#      the SendKeys (so the note never lands in the wrong window/tab).
#
# Why ancestor-pid + focus-first + HWND-identity guard: each Windows Terminal window
# is a SEPARATE WindowsTerminal.exe process here, so the ancestor pid uniquely
# identifies THIS session's window (Get-Process.MainWindowHandle would grab a
# process's first window, which is wrong when a host serves several). The window is
# generally not foreground while a tool runs, so we must focus it first and confirm
# the focus landed. The waker then guards the ~200ms gap by EXACT WINDOW IDENTITY:
# it types only if our specific HWND is foreground. A title-equality check can't be
# used — Claude Code's terminal title animates (spinner) and changes across /clear,
# so it would never match; the HWND check is the robust "right window" guard.
#
# Why detached + delayed: a blocking sleep here would hold the turn open, so /clear
# could never fire. The waker must outlive the turn. The short gap lets the turn end
# and /clear wipe the context first.
#
# Why TYPE the note (not `owl send`): after /clear the session sits IDLE awaiting user
# input. A spooled owl message cannot self-start a turn — it surfaces only during an
# already-active turn (PreToolUse hook) or in a live Monitor stream (whose buffer /clear
# wipes). So the prior "arrives as a waking poll message" design never woke the agent.
# A typed prompt + Enter deterministically starts a fresh turn AND is the re-seed.
$ErrorActionPreference = 'Stop'

# --- 1. Resolve agent_id ---
$owl = $env:OWL
if (-not $owl) { Write-Error 'OWL env not set — restart the session so the SessionStart hook runs.'; exit 1 }
$who = & $owl whoami 2>&1 | Out-String
if ($who -notmatch 'WHOAMI:([^\s]+)') {
  Write-Error "No perch for this session (whoami: $($who.Trim())). Run /spt:ready or /spt:live first — NOT clearing."
  exit 2
}
$id = $matches[1]

# --- 2. Identify (by ancestor pid), reliably focus, and HARD-verify the window ---
Add-Type @'
using System;
using System.Text;
using System.Runtime.InteropServices;
public class SC {
  [DllImport("user32.dll")] public static extern bool SetForegroundWindow(IntPtr h);
  [DllImport("user32.dll")] public static extern bool ShowWindow(IntPtr h, int n);
  [DllImport("user32.dll")] public static extern bool BringWindowToTop(IntPtr h);
  [DllImport("user32.dll")] public static extern bool IsIconic(IntPtr h);
  [DllImport("user32.dll")] public static extern IntPtr GetForegroundWindow();
  [DllImport("user32.dll")] static extern bool EnumWindows(EnumWindowsProc cb, IntPtr l);
  [DllImport("user32.dll")] static extern uint GetWindowThreadProcessId(IntPtr h, out uint pid);
  [DllImport("user32.dll")] static extern bool IsWindowVisible(IntPtr h);
  [DllImport("user32.dll")] static extern IntPtr GetWindow(IntPtr h, uint cmd);
  [DllImport("user32.dll")] static extern int GetWindowTextLength(IntPtr h);
  [DllImport("user32.dll", CharSet=CharSet.Unicode)] static extern int GetWindowText(IntPtr h, StringBuilder s, int max);
  [DllImport("user32.dll")] static extern bool AttachThreadInput(uint a, uint b, bool f);
  [DllImport("kernel32.dll")] static extern uint GetCurrentThreadId();
  delegate bool EnumWindowsProc(IntPtr h, IntPtr l);
  const uint GW_OWNER = 4;
  // First visible, titled, un-owned (top-level) window belonging to `target` pid.
  public static IntPtr FindForPid(uint target) {
    IntPtr found = IntPtr.Zero;
    EnumWindows((h, l) => {
      uint pid; GetWindowThreadProcessId(h, out pid);
      if (pid == target && IsWindowVisible(h) && GetWindow(h, GW_OWNER) == IntPtr.Zero && GetWindowTextLength(h) > 0) {
        found = h; return false; // stop enumeration
      }
      return true;
    }, IntPtr.Zero);
    return found;
  }
  // RELIABLY bring `h` to the foreground. A background process can't normally steal
  // focus (SetForegroundWindow only flashes), so attach our input queue to the
  // current foreground thread first — that lets SetForegroundWindow actually hold.
  // Returns true ONLY if `h` is genuinely the foreground window afterwards.
  public static bool FocusAndConfirm(IntPtr h) {
    IntPtr fg = GetForegroundWindow();
    uint dummy; uint fgThread = GetWindowThreadProcessId(fg, out dummy);
    uint myThread = GetCurrentThreadId();
    bool attached = (fgThread != myThread) && AttachThreadInput(myThread, fgThread, true);
    if (IsIconic(h)) ShowWindow(h, 9); // SW_RESTORE
    BringWindowToTop(h);
    SetForegroundWindow(h);
    bool ok = (GetForegroundWindow() == h);
    if (attached) AttachThreadInput(myThread, fgThread, false);
    return ok;
  }
  public static string TitleOf(IntPtr h) {
    int n = GetWindowTextLength(h);
    StringBuilder sb = new StringBuilder(n + 1);
    GetWindowText(h, sb, sb.Capacity);
    return sb.ToString();
  }
}
'@
# Walk the parent chain; the deepest ancestor pid that OWNS a visible top-level window
# is this session's terminal window (each WT window is its own process here).
$cur = $PID; $hwnd = [IntPtr]::Zero; $winpid = 0
for ($i = 0; $i -lt 8; $i++) {
  $ci = Get-CimInstance Win32_Process -Filter "ProcessId=$cur" -ErrorAction SilentlyContinue
  if (-not $ci) { break }
  $cand = [SC]::FindForPid([uint32]$cur)
  if ($cand -ne [IntPtr]::Zero) { $hwnd = $cand; $winpid = $cur; break }
  $cur = $ci.ParentProcessId
}
if ($hwnd -eq [IntPtr]::Zero) { Write-Error 'No ancestor-owned terminal window found in 8 hops — NOT clearing.'; exit 3 }

# Reliably focus the target (two attempts). If it will not hold the foreground, ABORT —
# never SendKeys into whatever window the user happens to be in.
$ok = [SC]::FocusAndConfirm($hwnd)
if (-not $ok) { Start-Sleep -Milliseconds 150; $ok = [SC]::FocusAndConfirm($hwnd) }
if (-not $ok -or [SC]::GetForegroundWindow() -ne $hwnd) {
  Write-Error "Could not hold the agent's terminal window (pid=$winpid hwnd=$hwnd) in the foreground — NOT clearing (no keys sent)."
  exit 4
}
$title = [SC]::TitleOf($hwnd)   # informational only (for the armed message)

# --- 3. Type /clear + Enter (queued; processes at turn-end) ---
# FINAL zero-gap guard immediately before sending — abort if focus drifted.
if ([SC]::GetForegroundWindow() -ne $hwnd) { Write-Error 'Lost foreground before send — NOT clearing.'; exit 4 }
$ws = New-Object -ComObject WScript.Shell
$ws.SendKeys('/clear{ENTER}')

# --- 4. Detached delayed waker (200ms) — re-foregrounds the captured HWND, then
#        TYPES the note ONLY IF that HWND is foreground AND its title is unchanged. ---
$hptr = $hwnd.ToInt64()
$note = 'Proceed with next steps'
$inner = @"
Start-Sleep -Milliseconds 200
Add-Type @'
using System;
using System.Runtime.InteropServices;
public class SCW {
  [DllImport("user32.dll")] public static extern bool SetForegroundWindow(IntPtr h);
  [DllImport("user32.dll")] public static extern bool ShowWindow(IntPtr h, int n);
  [DllImport("user32.dll")] public static extern bool IsIconic(IntPtr h);
  [DllImport("user32.dll")] public static extern IntPtr GetForegroundWindow();
}
'@
`$h = [IntPtr]$hptr
`$ws = New-Object -ComObject WScript.Shell
if ([SCW]::IsIconic(`$h)) { [SCW]::ShowWindow(`$h, 9) | Out-Null }
[SCW]::SetForegroundWindow(`$h) | Out-Null
try { `$ws.AppActivate($winpid) | Out-Null } catch {}
Start-Sleep -Milliseconds 120
# ABORT the SendKeys unless OUR EXACT window is the foreground window (right window only).
if ([SCW]::GetForegroundWindow() -ne `$h) { exit 0 }
`$ws.SendKeys('$note')
Start-Sleep -Milliseconds 150
`$ws.SendKeys('{ENTER}')
"@
$enc = [Convert]::ToBase64String([Text.Encoding]::Unicode.GetBytes($inner))
Start-Process -FilePath 'pwsh' -ArgumentList '-NoProfile','-NonInteractive','-EncodedCommand',$enc -WindowStyle Hidden | Out-Null

"SELF-CLEAR armed: id=$id winpid=$winpid hwnd=$hwnd title=`"$title`" — /clear queued; self-note typed in ~200ms (aborts if window/title changes)"

+  [DllImport("user32.dll")] public static extern bool BringWindowToTop(IntPtr h);

+  [DllImport("user32.dll")] static extern uint GetWindowThreadProcessId(IntPtr h, out uint pid);

+  [DllImport("user32.dll")] static extern bool AttachThreadInput(uint a, uint b, bool f);

+  [DllImport("kernel32.dll")] static extern uint GetCurrentThreadId();

+  public static bool FocusAndConfirm(IntPtr h) {

+    uint dummy; uint fgThread = GetWindowThreadProcessId(fg, out dummy);

+    uint myThread = GetCurrentThreadId();

+    bool attached = (fgThread != myThread) && AttachThreadInput(myThread, fgThread, true);

+    bool ok = (GetForegroundWindow() == h);

+    if (attached) AttachThreadInput(myThread, fgThread, false);

-`$ws = New-Object -ComObject WScript.Shell

-if ([SCW]::IsIconic(`$h)) { [SCW]::ShowWindow(`$h, 9) | Out-Null }

-try { `$ws.AppActivate($winpid) | Out-Null } catch {}

-# ABORT the SendKeys unless OUR EXACT window is the foreground window (right window only).

+# Reliably focus OUR window. ABORT (no keys) unless it genuinely holds the foreground.

+if (-not [SCW]::FocusAndConfirm(`$h)) { exit 0 }

+`$ws = New-Object -ComObject WScript.Shell


Updating header rationale + armed message.


#   2. Identify this session's terminal window by ANCESTOR PID: walk this process's
#      parent chain and find the visible top-level window owned by an ancestor pid
#      (the WindowsTerminal.exe hosting this tab). FOCUS it (the agent window is
#      usually NOT foreground when a tool runs), then VERIFY it actually came
#      foreground before doing anything — abort WITHOUT clearing if it didn't.
#      Capture its title bar text for the waker's safety check.
#   3. Type "/clear" + Enter  — QUEUED; fires only when this agent's turn ends.
#   4. Spawn a DETACHED waker that waits 200ms, re-focuses the captured HWND, and
#      — ONLY IF that HWND is foreground AND its title still matches the captured
#      title — TYPES the self-note as a fresh user prompt + Enter. Otherwise it ABORTS
#      the SendKeys (so the note never lands in the wrong window/tab).
#
# Why ancestor-pid + focus-first + HWND-identity guard: each Windows Terminal window
# is a SEPARATE WindowsTerminal.exe process here, so the ancestor pid uniquely
# identifies THIS session's window (Get-Process.MainWindowHandle would grab a
# process's first window, which is wrong when a host serves several). The window is
# generally not foreground while a tool runs, so we must focus it first and confirm
# the focus landed. The waker then guards the ~200ms gap by EXACT WINDOW IDENTITY:
# it types only if our specific HWND is foreground. A title-equality check can't be
# used — Claude Code's terminal title animates (spinner) and changes across /clear,
# so it would never match; the HWND check is the robust "right window" guard.

#   2. Identify this session's terminal window by ANCESTOR PID: walk this process's
#      parent chain and find the visible top-level window owned by an ancestor pid
#      (the WindowsTerminal.exe hosting this tab). RELIABLY focus it via
#      AttachThreadInput, then HARD-VERIFY it genuinely holds the foreground. If it
#      does NOT, abort WITHOUT clearing and WITHOUT sending any keys.
#   3. Re-verify foreground with ZERO gap, then type "/clear{ENTER}" in ONE SendKeys
#      — QUEUED; fires only when this agent's turn ends.
#   4. Spawn a DETACHED waker that waits 200ms, reliably re-focuses the captured HWND,
#      and — ONLY IF that exact HWND genuinely holds the foreground — TYPES the
#      self-note + Enter as a fresh user prompt. Otherwise it ABORTS the SendKeys.
#
# Why AttachThreadInput + hard-verify + single-send (the bug this fixes): SendKeys
# targets WHATEVER window is foreground at SEND time, not at check time. A background
# process cannot normally steal focus — SetForegroundWindow/AppActivate only FLASH the
# window forward transiently. The old code read GetForegroundWindow during that flash
# (check passed), focus then snapped back to the user's window, and SendKeys landed in
# the WRONG (user's) window — a TOCTOU race that typed into whatever the user was doing.
# Fix: attach our input queue to the current foreground thread so SetForegroundWindow
# actually HOLDS, confirm the target is truly foreground, re-confirm with zero gap, and
# send /clear+Enter as a single call. If the steal can't hold, we abort — never type
# blind. Each Windows Terminal window is its own process here, so the ancestor pid
# uniquely identifies THIS session's window (MainWindowHandle would grab a host's first
# window — wrong when one host serves several).


#   2. Identify this session's terminal window by ANCESTOR PID: walk this process's
#      parent chain and find the visible top-level window owned by an ancestor pid
#      (the WindowsTerminal.exe hosting this tab). RELIABLY focus it via
#      AttachThreadInput, then HARD-VERIFY it genuinely holds the foreground. If it
#      does NOT, abort WITHOUT clearing and WITHOUT sending any keys.
#   3. Re-verify foreground with ZERO gap, then type "/clear{ENTER}" in ONE SendKeys
#      — QUEUED; fires only when this agent's turn ends.
#   4. Spawn a DETACHED waker that waits 200ms, reliably re-focuses the captured HWND,
#      and — ONLY IF that exact HWND genuinely holds the foreground — TYPES the
#      self-note + Enter as a fresh user prompt. Otherwise it ABORTS the SendKeys.
#
# Why AttachThreadInput + hard-verify + single-send (the bug this fixes): SendKeys
# targets WHATEVER window is foreground at SEND time, not at check time. A background
# process cannot normally steal focus — SetForegroundWindow/AppActivate only FLASH the
# window forward transiently. The old code read GetForegroundWindow during that flash
# (check passed), focus then snapped back to the user's window, and SendKeys landed in
# the WRONG (user's) window — a TOCTOU race that typed into whatever the user was doing.
# Fix: attach our input queue to the current foreground thread so SetForegroundWindow
# actually HOLDS, confirm the target is truly foreground, re-confirm with zero gap, and
# send /clear+Enter as a single call. If the steal can't hold, we abort — never type
# blind. Each Windows Terminal window is its own process here, so the ancestor pid
# uniquely identifies THIS session's window (MainWindowHandle would grab a host's first
# window — wrong when one host serves several).

# self-clear.ps1 — one-shot self-clear + delayed self-wake.
#
# Order (per design):
#   1. Resolve this session's agent_id via `owl whoami` (context-independent; survives /clear).
#   2. Identify this session's terminal window by ANCESTOR PID: walk this process's
#      parent chain and find the visible top-level window owned by an ancestor pid
#      (the WindowsTerminal.exe hosting this tab). FOCUS it (the agent window is
#      usually NOT foreground when a tool runs), then VERIFY it actually came
#      foreground before doing anything — abort WITHOUT clearing if it didn't.
#      Capture its title bar text for the waker's safety check.
#   3. Type "/clear" + Enter  — QUEUED; fires only when this agent's turn ends.
#   4. Spawn a DETACHED waker that waits 200ms, re-focuses the captured HWND, and
#      — ONLY IF that HWND is foreground AND its title still matches the captured
#      title — TYPES the self-note as a fresh user prompt + Enter. Otherwise it ABORTS
#      the SendKeys (so the note never lands in the wrong window/tab).
#
# Why ancestor-pid + focus-first + HWND-identity guard: each Windows Terminal window
# is a SEPARATE WindowsTerminal.exe process here, so the ancestor pid uniquely
# identifies THIS session's window (Get-Process.MainWindowHandle would grab a
# process's first window, which is wrong when a host serves several). The window is
# generally not foreground while a tool runs, so we must focus it first and confirm
# the focus landed. The waker then guards the ~200ms gap by EXACT WINDOW IDENTITY:
# it types only if our specific HWND is foreground. A title-equality check can't be
# used — Claude Code's terminal title animates (spinner) and changes across /clear,
# so it would never match; the HWND check is the robust "right window" guard.
#
# Why detached + delayed: a blocking sleep here would hold the turn open, so /clear
# could never fire. The waker must outlive the turn. The short gap lets the turn end
# and /clear wipe the context first.
#
# Why TYPE the note (not `owl send`): after /clear the session sits IDLE awaiting user
# input. A spooled owl message cannot self-start a turn — it surfaces only during an
# already-active turn (PreToolUse hook) or in a live Monitor stream (whose buffer /clear
# wipes). So the prior "arrives as a waking poll message" design never woke the agent.
# A typed prompt + Enter deterministically starts a fresh turn AND is the re-seed.
$ErrorActionPreference = 'Stop'

# --- 1. Resolve agent_id ---
$owl = $env:OWL
if (-not $owl) { Write-Error 'OWL env not set — restart the session so the SessionStart hook runs.'; exit 1 }
$who = & $owl whoami 2>&1 | Out-String
if ($who -notmatch 'WHOAMI:([^\s]+)') {
  Write-Error "No perch for this session (whoami: $($who.Trim())). Run /spt:ready or /spt:live first — NOT clearing."
  exit 2
}
$id = $matches[1]

# --- 2. Identify (by ancestor pid), reliably focus, and HARD-verify the window ---
Add-Type @'
using System;
using System.Text;
using System.Runtime.InteropServices;
public class SC {
  [DllImport("user32.dll")] public static extern bool SetForegroundWindow(IntPtr h);
  [DllImport("user32.dll")] public static extern bool ShowWindow(IntPtr h, int n);
  [DllImport("user32.dll")] public static extern bool BringWindowToTop(IntPtr h);
  [DllImport("user32.dll")] public static extern bool IsIconic(IntPtr h);
  [DllImport("user32.dll")] public static extern IntPtr GetForegroundWindow();
  [DllImport("user32.dll")] static extern bool EnumWindows(EnumWindowsProc cb, IntPtr l);
  [DllImport("user32.dll")] static extern uint GetWindowThreadProcessId(IntPtr h, out uint pid);
  [DllImport("user32.dll")] static extern bool IsWindowVisible(IntPtr h);
  [DllImport("user32.dll")] static extern IntPtr GetWindow(IntPtr h, uint cmd);
  [DllImport("user32.dll")] static extern int GetWindowTextLength(IntPtr h);
  [DllImport("user32.dll", CharSet=CharSet.Unicode)] static extern int GetWindowText(IntPtr h, StringBuilder s, int max);
  [DllImport("user32.dll")] static extern bool AttachThreadInput(uint a, uint b, bool f);
  [DllImport("kernel32.dll")] static extern uint GetCurrentThreadId();
  delegate bool EnumWindowsProc(IntPtr h, IntPtr l);
  const uint GW_OWNER = 4;
  // First visible, titled, un-owned (top-level) window belonging to `target` pid.
  public static IntPtr FindForPid(uint target) {
    IntPtr found = IntPtr.Zero;
    EnumWindows((h, l) => {
      uint pid; GetWindowThreadProcessId(h, out pid);
      if (pid == target && IsWindowVisible(h) && GetWindow(h, GW_OWNER) == IntPtr.Zero && GetWindowTextLength(h) > 0) {
        found = h; return false; // stop enumeration
      }
      return true;
    }, IntPtr.Zero);
    return found;
  }
  // RELIABLY bring `h` to the foreground. A background process can't normally steal
  // focus (SetForegroundWindow only flashes), so attach our input queue to the
  // current foreground thread first — that lets SetForegroundWindow actually hold.
  // Returns true ONLY if `h` is genuinely the foreground window afterwards.
  public static bool FocusAndConfirm(IntPtr h) {
    IntPtr fg = GetForegroundWindow();
    uint dummy; uint fgThread = GetWindowThreadProcessId(fg, out dummy);
    uint myThread = GetCurrentThreadId();
    bool attached = (fgThread != myThread) && AttachThreadInput(myThread, fgThread, true);
    if (IsIconic(h)) ShowWindow(h, 9); // SW_RESTORE
    BringWindowToTop(h);
    SetForegroundWindow(h);
    bool ok = (GetForegroundWindow() == h);
    if (attached) AttachThreadInput(myThread, fgThread, false);
    return ok;
  }
  public static string TitleOf(IntPtr h) {
    int n = GetWindowTextLength(h);
    StringBuilder sb = new StringBuilder(n + 1);
    GetWindowText(h, sb, sb.Capacity);
    return sb.ToString();
  }
}
'@
# Walk the parent chain; the deepest ancestor pid that OWNS a visible top-level window
# is this session's terminal window (each WT window is its own process here).
$cur = $PID; $hwnd = [IntPtr]::Zero; $winpid = 0
for ($i = 0; $i -lt 8; $i++) {
  $ci = Get-CimInstance Win32_Process -Filter "ProcessId=$cur" -ErrorAction SilentlyContinue
  if (-not $ci) { break }
  $cand = [SC]::FindForPid([uint32]$cur)
  if ($cand -ne [IntPtr]::Zero) { $hwnd = $cand; $winpid = $cur; break }
  $cur = $ci.ParentProcessId
}
if ($hwnd -eq [IntPtr]::Zero) { Write-Error 'No ancestor-owned terminal window found in 8 hops — NOT clearing.'; exit 3 }

# Reliably focus the target (two attempts). If it will not hold the foreground, ABORT —
# never SendKeys into whatever window the user happens to be in.
$ok = [SC]::FocusAndConfirm($hwnd)
if (-not $ok) { Start-Sleep -Milliseconds 150; $ok = [SC]::FocusAndConfirm($hwnd) }
if (-not $ok -or [SC]::GetForegroundWindow() -ne $hwnd) {
  Write-Error "Could not hold the agent's terminal window (pid=$winpid hwnd=$hwnd) in the foreground — NOT clearing (no keys sent)."
  exit 4
}
$title = [SC]::TitleOf($hwnd)   # informational only (for the armed message)

# --- 3. Type /clear + Enter (queued; processes at turn-end) ---
# FINAL zero-gap guard immediately before sending — abort if focus drifted.
if ([SC]::GetForegroundWindow() -ne $hwnd) { Write-Error 'Lost foreground before send — NOT clearing.'; exit 4 }
$ws = New-Object -ComObject WScript.Shell
$ws.SendKeys('/clear{ENTER}')

# --- 4. Detached delayed waker (200ms) — re-foregrounds the captured HWND, then
#        TYPES the note ONLY IF that HWND is foreground AND its title is unchanged. ---
$hptr = $hwnd.ToInt64()
$note = 'Proceed with next steps'
$inner = @"
Start-Sleep -Milliseconds 200
Add-Type @'
using System;
using System.Runtime.InteropServices;
public class SCW {
  [DllImport("user32.dll")] public static extern bool SetForegroundWindow(IntPtr h);
  [DllImport("user32.dll")] public static extern bool ShowWindow(IntPtr h, int n);
  [DllImport("user32.dll")] public static extern bool BringWindowToTop(IntPtr h);
  [DllImport("user32.dll")] public static extern bool IsIconic(IntPtr h);
  [DllImport("user32.dll")] public static extern IntPtr GetForegroundWindow();
  [DllImport("user32.dll")] static extern uint GetWindowThreadProcessId(IntPtr h, out uint pid);
  [DllImport("user32.dll")] static extern bool AttachThreadInput(uint a, uint b, bool f);
  [DllImport("kernel32.dll")] static extern uint GetCurrentThreadId();
  public static bool FocusAndConfirm(IntPtr h) {
    IntPtr fg = GetForegroundWindow();
    uint dummy; uint fgThread = GetWindowThreadProcessId(fg, out dummy);
    uint myThread = GetCurrentThreadId();
    bool attached = (fgThread != myThread) && AttachThreadInput(myThread, fgThread, true);
    if (IsIconic(h)) ShowWindow(h, 9);
    BringWindowToTop(h);
    SetForegroundWindow(h);
    bool ok = (GetForegroundWindow() == h);
    if (attached) AttachThreadInput(myThread, fgThread, false);
    return ok;
  }
}
'@
`$h = [IntPtr]$hptr
# Reliably focus OUR window. ABORT (no keys) unless it genuinely holds the foreground.
if (-not [SCW]::FocusAndConfirm(`$h)) { exit 0 }
if ([SCW]::GetForegroundWindow() -ne `$h) { exit 0 }
`$ws = New-Object -ComObject WScript.Shell
`$ws.SendKeys('$note{ENTER}')
"@
$enc = [Convert]::ToBase64String([Text.Encoding]::Unicode.GetBytes($inner))
Start-Process -FilePath 'pwsh' -ArgumentList '-NoProfile','-NonInteractive','-EncodedCommand',$enc -WindowStyle Hidden | Out-Null

"SELF-CLEAR armed: id=$id winpid=$winpid hwnd=$hwnd title=`"$title`" — /clear queued; self-note typed in ~200ms (aborts if window/title changes)"

-#      (the WindowsTerminal.exe hosting this tab). FOCUS it (the agent window is

-#      usually NOT foreground when a tool runs), then VERIFY it actually came

-#      foreground before doing anything — abort WITHOUT clearing if it didn't.

-#      Capture its title bar text for the waker's safety check.

-#   3. Type "/clear" + Enter  — QUEUED; fires only when this agent's turn ends.

-#   4. Spawn a DETACHED waker that waits 200ms, re-focuses the captured HWND, and

-#      — ONLY IF that HWND is foreground AND its title still matches the captured

-#      title — TYPES the self-note as a fresh user prompt + Enter. Otherwise it ABORTS

-#      the SendKeys (so the note never lands in the wrong window/tab).

+#      (the WindowsTerminal.exe hosting this tab). RELIABLY focus it via

+#      AttachThreadInput, then HARD-VERIFY it genuinely holds the foreground. If it

+#      does NOT, abort WITHOUT clearing and WITHOUT sending any keys.

+#   3. Re-verify foreground with ZERO gap, then type "/clear{ENTER}" in ONE SendKeys

+#      — QUEUED; fires only when this agent's turn ends.

+#   4. Spawn a DETACHED waker that waits 200ms, reliably re-focuses the captured HWND,

+#      and — ONLY IF that exact HWND genuinely holds the foreground — TYPES the

+#      self-note + Enter as a fresh user prompt. Otherwise it ABORTS the SendKeys.

-# Why ancestor-pid + focus-first + HWND-identity guard: each Windows Terminal window

-# is a SEPARATE WindowsTerminal.exe process here, so the ancestor pid uniquely

-# identifies THIS session's window (Get-Process.MainWindowHandle would grab a

-# process's first window, which is wrong when a host serves several). The window is

-# generally not foreground while a tool runs, so we must focus it first and confirm

-# the focus landed. The waker then guards the ~200ms gap by EXACT WINDOW IDENTITY:

-# it types only if our specific HWND is foreground. A title-equality check can't be

-# used — Claude Code's terminal title animates (spinner) and changes across /clear,

-# so it would never match; the HWND check is the robust "right window" guard.

+# Why AttachThreadInput + hard-verify + single-send (the bug this fixes): SendKeys

+# targets WHATEVER window is foreground at SEND time, not at check time. A background

+# process cannot normally steal focus — SetForegroundWindow/AppActivate only FLASH the

+# window forward transiently. The old code read GetForegroundWindow during that flash

+# (check passed), focus then snapped back to the user's window, and SendKeys landed in

+# the WRONG (user's) window — a TOCTOU race that typed into whatever the user was doing.

+# Fix: attach our input queue to the current foreground thread so SetForegroundWindow

+# actually HOLDS, confirm the target is truly foreground, re-confirm with zero gap, and

+# send /clear+Enter as a single call. If the steal can't hold, we abort — never type

+# blind. Each Windows Terminal window is its own process here, so the ancestor pid

+# uniquely identifies THIS session's window (MainWindowHandle would grab a host's first

+# window — wrong when one host serves several).


"SELF-CLEAR armed: id=$id winpid=$winpid hwnd=$hwnd title=`"$title`" — /clear queued; self-note typed in ~200ms (aborts if window/title changes)"

"SELF-CLEAR armed: id=$id winpid=$winpid hwnd=$hwnd title=`"$title`" — focus held + /clear queued; self-note typed in ~200ms (waker aborts unless our window holds foreground)"


# self-clear.ps1 — one-shot self-clear + delayed self-wake.
#
# Order (per design):
#   1. Resolve this session's agent_id via `owl whoami` (context-independent; survives /clear).
#   2. Identify this session's terminal window by ANCESTOR PID: walk this process's
#      parent chain and find the visible top-level window owned by an ancestor pid
#      (the WindowsTerminal.exe hosting this tab). RELIABLY focus it via
#      AttachThreadInput, then HARD-VERIFY it genuinely holds the foreground. If it
#      does NOT, abort WITHOUT clearing and WITHOUT sending any keys.
#   3. Re-verify foreground with ZERO gap, then type "/clear{ENTER}" in ONE SendKeys
#      — QUEUED; fires only when this agent's turn ends.
#   4. Spawn a DETACHED waker that waits 200ms, reliably re-focuses the captured HWND,
#      and — ONLY IF that exact HWND genuinely holds the foreground — TYPES the
#      self-note + Enter as a fresh user prompt. Otherwise it ABORTS the SendKeys.
#
# Why AttachThreadInput + hard-verify + single-send (the bug this fixes): SendKeys
# targets WHATEVER window is foreground at SEND time, not at check time. A background
# process cannot normally steal focus — SetForegroundWindow/AppActivate only FLASH the
# window forward transiently. The old code read GetForegroundWindow during that flash
# (check passed), focus then snapped back to the user's window, and SendKeys landed in
# the WRONG (user's) window — a TOCTOU race that typed into whatever the user was doing.
# Fix: attach our input queue to the current foreground thread so SetForegroundWindow
# actually HOLDS, confirm the target is truly foreground, re-confirm with zero gap, and
# send /clear+Enter as a single call. If the steal can't hold, we abort — never type
# blind. Each Windows Terminal window is its own process here, so the ancestor pid
# uniquely identifies THIS session's window (MainWindowHandle would grab a host's first
# window — wrong when one host serves several).
#
# Why detached + delayed: a blocking sleep here would hold the turn open, so /clear
# could never fire. The waker must outlive the turn. The short gap lets the turn end
# and /clear wipe the context first.
#
# Why TYPE the note (not `owl send`): after /clear the session sits IDLE awaiting user
# input. A spooled owl message cannot self-start a turn — it surfaces only during an
# already-active turn (PreToolUse hook) or in a live Monitor stream (whose buffer /clear
# wipes). So the prior "arrives as a waking poll message" design never woke the agent.
# A typed prompt + Enter deterministically starts a fresh turn AND is the re-seed.
$ErrorActionPreference = 'Stop'

# --- 1. Resolve agent_id ---
$owl = $env:OWL
if (-not $owl) { Write-Error 'OWL env not set — restart the session so the SessionStart hook runs.'; exit 1 }
$who = & $owl whoami 2>&1 | Out-String
if ($who -notmatch 'WHOAMI:([^\s]+)') {
  Write-Error "No perch for this session (whoami: $($who.Trim())). Run /spt:ready or /spt:live first — NOT clearing."
  exit 2
}
$id = $matches[1]

# --- 2. Identify (by ancestor pid), reliably focus, and HARD-verify the window ---
Add-Type @'
using System;
using System.Text;
using System.Runtime.InteropServices;
public class SC {
  [DllImport("user32.dll")] public static extern bool SetForegroundWindow(IntPtr h);
  [DllImport("user32.dll")] public static extern bool ShowWindow(IntPtr h, int n);
  [DllImport("user32.dll")] public static extern bool BringWindowToTop(IntPtr h);
  [DllImport("user32.dll")] public static extern bool IsIconic(IntPtr h);
  [DllImport("user32.dll")] public static extern IntPtr GetForegroundWindow();
  [DllImport("user32.dll")] static extern bool EnumWindows(EnumWindowsProc cb, IntPtr l);
  [DllImport("user32.dll")] static extern uint GetWindowThreadProcessId(IntPtr h, out uint pid);
  [DllImport("user32.dll")] static extern bool IsWindowVisible(IntPtr h);
  [DllImport("user32.dll")] static extern IntPtr GetWindow(IntPtr h, uint cmd);
  [DllImport("user32.dll")] static extern int GetWindowTextLength(IntPtr h);
  [DllImport("user32.dll", CharSet=CharSet.Unicode)] static extern int GetWindowText(IntPtr h, StringBuilder s, int max);
  [DllImport("user32.dll")] static extern bool AttachThreadInput(uint a, uint b, bool f);
  [DllImport("kernel32.dll")] static extern uint GetCurrentThreadId();
  delegate bool EnumWindowsProc(IntPtr h, IntPtr l);
  const uint GW_OWNER = 4;
  // First visible, titled, un-owned (top-level) window belonging to `target` pid.
  public static IntPtr FindForPid(uint target) {
    IntPtr found = IntPtr.Zero;
    EnumWindows((h, l) => {
      uint pid; GetWindowThreadProcessId(h, out pid);
      if (pid == target && IsWindowVisible(h) && GetWindow(h, GW_OWNER) == IntPtr.Zero && GetWindowTextLength(h) > 0) {
        found = h; return false; // stop enumeration
      }
      return true;
    }, IntPtr.Zero);
    return found;
  }
  // RELIABLY bring `h` to the foreground. A background process can't normally steal
  // focus (SetForegroundWindow only flashes), so attach our input queue to the
  // current foreground thread first — that lets SetForegroundWindow actually hold.
  // Returns true ONLY if `h` is genuinely the foreground window afterwards.
  public static bool FocusAndConfirm(IntPtr h) {
    IntPtr fg = GetForegroundWindow();
    uint dummy; uint fgThread = GetWindowThreadProcessId(fg, out dummy);
    uint myThread = GetCurrentThreadId();
    bool attached = (fgThread != myThread) && AttachThreadInput(myThread, fgThread, true);
    if (IsIconic(h)) ShowWindow(h, 9); // SW_RESTORE
    BringWindowToTop(h);
    SetForegroundWindow(h);
    bool ok = (GetForegroundWindow() == h);
    if (attached) AttachThreadInput(myThread, fgThread, false);
    return ok;
  }
  public static string TitleOf(IntPtr h) {
    int n = GetWindowTextLength(h);
    StringBuilder sb = new StringBuilder(n + 1);
    GetWindowText(h, sb, sb.Capacity);
    return sb.ToString();
  }
}
'@
# Walk the parent chain; the deepest ancestor pid that OWNS a visible top-level window
# is this session's terminal window (each WT window is its own process here).
$cur = $PID; $hwnd = [IntPtr]::Zero; $winpid = 0
for ($i = 0; $i -lt 8; $i++) {
  $ci = Get-CimInstance Win32_Process -Filter "ProcessId=$cur" -ErrorAction SilentlyContinue
  if (-not $ci) { break }
  $cand = [SC]::FindForPid([uint32]$cur)
  if ($cand -ne [IntPtr]::Zero) { $hwnd = $cand; $winpid = $cur; break }
  $cur = $ci.ParentProcessId
}
if ($hwnd -eq [IntPtr]::Zero) { Write-Error 'No ancestor-owned terminal window found in 8 hops — NOT clearing.'; exit 3 }

# Reliably focus the target (two attempts). If it will not hold the foreground, ABORT —
# never SendKeys into whatever window the user happens to be in.
$ok = [SC]::FocusAndConfirm($hwnd)
if (-not $ok) { Start-Sleep -Milliseconds 150; $ok = [SC]::FocusAndConfirm($hwnd) }
if (-not $ok -or [SC]::GetForegroundWindow() -ne $hwnd) {
  Write-Error "Could not hold the agent's terminal window (pid=$winpid hwnd=$hwnd) in the foreground — NOT clearing (no keys sent)."
  exit 4
}
$title = [SC]::TitleOf($hwnd)   # informational only (for the armed message)

# --- 3. Type /clear + Enter (queued; processes at turn-end) ---
# FINAL zero-gap guard immediately before sending — abort if focus drifted.
if ([SC]::GetForegroundWindow() -ne $hwnd) { Write-Error 'Lost foreground before send — NOT clearing.'; exit 4 }
$ws = New-Object -ComObject WScript.Shell
$ws.SendKeys('/clear{ENTER}')

# --- 4. Detached delayed waker (200ms) — re-foregrounds the captured HWND, then
#        TYPES the note ONLY IF that HWND is foreground AND its title is unchanged. ---
$hptr = $hwnd.ToInt64()
$note = 'Proceed with next steps'
$inner = @"
Start-Sleep -Milliseconds 200
Add-Type @'
using System;
using System.Runtime.InteropServices;
public class SCW {
  [DllImport("user32.dll")] public static extern bool SetForegroundWindow(IntPtr h);
  [DllImport("user32.dll")] public static extern bool ShowWindow(IntPtr h, int n);
  [DllImport("user32.dll")] public static extern bool BringWindowToTop(IntPtr h);
  [DllImport("user32.dll")] public static extern bool IsIconic(IntPtr h);
  [DllImport("user32.dll")] public static extern IntPtr GetForegroundWindow();
  [DllImport("user32.dll")] static extern uint GetWindowThreadProcessId(IntPtr h, out uint pid);
  [DllImport("user32.dll")] static extern bool AttachThreadInput(uint a, uint b, bool f);
  [DllImport("kernel32.dll")] static extern uint GetCurrentThreadId();
  public static bool FocusAndConfirm(IntPtr h) {
    IntPtr fg = GetForegroundWindow();
    uint dummy; uint fgThread = GetWindowThreadProcessId(fg, out dummy);
    uint myThread = GetCurrentThreadId();
    bool attached = (fgThread != myThread) && AttachThreadInput(myThread, fgThread, true);
    if (IsIconic(h)) ShowWindow(h, 9);
    BringWindowToTop(h);
    SetForegroundWindow(h);
    bool ok = (GetForegroundWindow() == h);
    if (attached) AttachThreadInput(myThread, fgThread, false);
    return ok;
  }
}
'@
`$h = [IntPtr]$hptr
# Reliably focus OUR window. ABORT (no keys) unless it genuinely holds the foreground.
if (-not [SCW]::FocusAndConfirm(`$h)) { exit 0 }
if ([SCW]::GetForegroundWindow() -ne `$h) { exit 0 }
`$ws = New-Object -ComObject WScript.Shell
`$ws.SendKeys('$note{ENTER}')
"@
$enc = [Convert]::ToBase64String([Text.Encoding]::Unicode.GetBytes($inner))
Start-Process -FilePath 'pwsh' -ArgumentList '-NoProfile','-NonInteractive','-EncodedCommand',$enc -WindowStyle Hidden | Out-Null

"SELF-CLEAR armed: id=$id winpid=$winpid hwnd=$hwnd title=`"$title`" — /clear queued; self-note typed in ~200ms (aborts if window/title changes)"

-"SELF-CLEAR armed: id=$id winpid=$winpid hwnd=$hwnd title=`"$title`" — /clear queued; self-note typed in ~200ms (aborts if window/title changes)"

+"SELF-CLEAR armed: id=$id winpid=$winpid hwnd=$hwnd title=`"$title`" — focus held + /clear queued; self-note typed in ~200ms (waker aborts unless our window holds foreground)"


SKILL.md to match.


2. Walks the parent chain (`pwsh → claude.exe → … → WindowsTerminal`) to the ancestor **pid that owns a visible top-level window** (enumerated by owner pid — not the program's default "main window", which is wrong when a host serves several windows). Since each WT window is its own process, that pid uniquely identifies this session's window. **Focuses it** (the agent window usually isn't foreground while a tool runs) and **verifies the focus landed** — aborts WITHOUT clearing if it can't bring the window forward.
3. Types `/clear` + Enter — **queued**; only fires when this turn ends.
4. Spawns a **detached** waker: waits 200ms (past turn-end + wipe), re-focuses the captured `hwnd`, and — **only if that exact HWND is the foreground window** — **types** `Proceed with next steps` + Enter as a fresh user prompt. Wrong window focused → it aborts the SendKeys (no title-match: Claude Code's terminal title animates and changes across `/clear`, so HWND identity is the reliable "right window" guard).

2. Walks the parent chain (`pwsh → claude.exe → … → WindowsTerminal`) to the ancestor **pid that owns a visible top-level window** (enumerated by owner pid — not the program's default "main window", which is wrong when a host serves several windows). Since each WT window is its own process, that pid uniquely identifies this session's window. **Reliably focuses it** via `AttachThreadInput` (a background process can't otherwise hold the foreground) and **hard-verifies it genuinely holds the foreground** — aborts WITHOUT clearing and WITHOUT sending any keys if it can't.
3. Re-verifies foreground with **zero gap**, then types `/clear{ENTER}` in one SendKeys — **queued**; only fires when this turn ends.
4. Spawns a **detached** waker: waits 200ms (past turn-end + wipe), reliably re-focuses the captured `hwnd`, and — **only if that exact HWND genuinely holds the foreground** — **types** `Proceed with next steps` + Enter as a fresh user prompt. Otherwise it aborts the SendKeys (HWND identity is the guard; no title-match — Claude Code's terminal title animates and changes across `/clear`).

Why this matters: `SendKeys` hits whatever window is foreground *at send time*. The fix attaches our input queue to the foreground thread so the focus steal actually holds, confirms the target is truly foreground, and aborts rather than type blind — so the note can never leak into the window you're working in.


The file C:\Users\decid\.claude\skills\self-clear\SKILL.md has been updated successfully. (file state is current in your context — no need to Read it back)

2. Walks the parent chain (`pwsh → claude.exe → … → WindowsTerminal`) to the ancestor **pid that owns a visible top-level window** (enumerated by owner pid — not the program's default "main window", which is wrong when a host serves several windows). Since each WT window is its own process, that pid uniquely identifies this session's window. **Reliably focuses it** via `AttachThreadInput` (a background process can't otherwise hold the foreground) and **hard-verifies it genuinely holds the foreground** — aborts WITHOUT clearing and WITHOUT sending any keys if it can't.
3. Re-verifies foreground with **zero gap**, then types `/clear{ENTER}` in one SendKeys — **queued**; only fires when this turn ends.
4. Spawns a **detached** waker: waits 200ms (past turn-end + wipe), reliably re-focuses the captured `hwnd`, and — **only if that exact HWND genuinely holds the foreground** — **types** `Proceed with next steps` + Enter as a fresh user prompt. Otherwise it aborts the SendKeys (HWND identity is the guard; no title-match — Claude Code's terminal title animates and changes across `/clear`).

Why this matters: `SendKeys` hits whatever window is foreground *at send time*. The fix attaches our input queue to the foreground thread so the focus steal actually holds, confirms the target is truly foreground, and aborts rather than type blind — so the note can never leak into the window you're working in.

---
name: self-clear
description: Clear the calling agent's own Claude Code context with /clear, then re-seed it with a user note so it wakes and resumes work after the wipe. Use when the user says "self-clear", "/self-clear", "clear myself", "clear and continue", or "reset my context and keep going".
allowed-tools: [PowerShell]
---

# /self-clear

Wipes THIS agent's conversation (`/clear`), then ~200ms later TYPES a self-note
as a fresh user prompt so the agent wakes on a clean context and proceeds.

## Run it

One script does everything. Just run:

```powershell
& "C:\Users\decid\.claude\skills\self-clear\scripts\self-clear.ps1"
```

Expect: `SELF-CLEAR armed: id=<agent_id> winpid=<n> hwnd=<n> — /clear queued; self-note typed in ~200ms`

Then stop — end your turn promptly so the queued `/clear` fires.

## What the script does

1. `owl whoami` → resolves this session's `agent_id` (context-independent, survives the wipe). No perch → it aborts WITHOUT clearing and tells you to `/spt:ready` or `/spt:live` first.
2. Walks the parent chain (`pwsh → claude.exe → … → WindowsTerminal`) to the ancestor **pid that owns a visible top-level window** (enumerated by owner pid — not the program's default "main window", which is wrong when a host serves several windows). Since each WT window is its own process, that pid uniquely identifies this session's window. **Focuses it** (the agent window usually isn't foreground while a tool runs) and **verifies the focus landed** — aborts WITHOUT clearing if it can't bring the window forward.
3. Types `/clear` + Enter — **queued**; only fires when this turn ends.
4. Spawns a **detached** waker: waits 200ms (past turn-end + wipe), re-focuses the captured `hwnd`, and — **only if that exact HWND is the foreground window** — **types** `Proceed with next steps` + Enter as a fresh user prompt. Wrong window focused → it aborts the SendKeys (no title-match: Claude Code's terminal title animates and changes across `/clear`, so HWND identity is the reliable "right window" guard).

The delay + detachment are deliberate: the prompt must be typed **after**
`/clear` takes effect, so it lands on the clean context and starts a new turn.

Why typed, not `owl send`: after `/clear` the session sits idle awaiting user
input. A spooled owl message can't self-start a turn — it surfaces only during an
already-active turn (PreToolUse hook) or in a live Monitor stream (whose buffer
`/clear` wipes). So a self-addressed owl note never woke the agent. A typed prompt
deterministically starts a turn AND is the re-seed.

## Caveats

- Focus targets the window owned by this session's ancestor pid (enumerated by owner pid). It still can't pick a specific tab when one window hosts multiple tabs; clean when the agent's tab is active.
- The waker types only when our exact window is foreground. If you switch to a different window/tab during the ~200ms gap, the wake-note SendKeys is aborted (the agent stays cleared but unwoken) — stay in the agent window for the wake.
- The note is typed via SendKeys, so the terminal must stay the foreground-able window of `winpid`. Don't close/replace the terminal in the gap.
- SendKeys reserves `+ ^ % ~ ( ) { } [ ]`; the default note text uses none. If you customize the note, escape those chars.
- The waker waits only ~200ms after the script returns, so stop promptly — if your turn runs longer, the prompt could be typed before the wipe completes.

-2. Walks the parent chain (`pwsh → claude.exe → … → WindowsTerminal`) to the ancestor **pid that owns a visible top-level window** (enumerated by owner pid — not the program's default "main window", which is wrong when a host serves several windows). Since each WT window is its own process, that pid uniquely identifies this session's window. **Focuses it** (the agent window usually isn't foreground while a tool runs) and **verifies the focus landed** — aborts WITHOUT clearing if it can't bring the window forward.

-3. Types `/clear` + Enter — **queued**; only fires when this turn ends.

-4. Spawns a **detached** waker: waits 200ms (past turn-end + wipe), re-focuses the captured `hwnd`, and — **only if that exact HWND is the foreground window** — **types** `Proceed with next steps` + Enter as a fresh user prompt. Wrong window focused → it aborts the SendKeys (no title-match: Claude Code's terminal title animates and changes across `/clear`, so HWND identity is the reliable "right window" guard).

+2. Walks the parent chain (`pwsh → claude.exe → … → WindowsTerminal`) to the ancestor **pid that owns a visible top-level window** (enumerated by owner pid — not the program's default "main window", which is wrong when a host serves several windows). Since each WT window is its own process, that pid uniquely identifies this session's window. **Reliably focuses it** via `AttachThreadInput` (a background process can't otherwise hold the foreground) and **hard-verifies it genuinely holds the foreground** — aborts WITHOUT clearing and WITHOUT sending any keys if it can't.

+3. Re-verifies foreground with **zero gap**, then types `/clear{ENTER}` in one SendKeys — **queued**; only fires when this turn ends.

+4. Spawns a **detached** waker: waits 200ms (past turn-end + wipe), reliably re-focuses the captured `hwnd`, and — **only if that exact HWND genuinely holds the foreground** — **types** `Proceed with next steps` + Enter as a fresh user prompt. Otherwise it aborts the SendKeys (HWND identity is the guard; no title-match — Claude Code's terminal title animates and changes across `/clear`).

+Why this matters: `SendKeys` hits whatever window is foreground *at send time*. The fix attaches our input queue to the foreground thread so the focus steal actually holds, confirms the target is truly foreground, and aborts rather than type blind — so the note can never leak into the window you're working in.


PowerShell

$f='C:\Users\decid\.claude\skills\self-clear\scripts\self-clear.ps1'
$e=$null;[System.Management.Automation.Language.Parser]::ParseFile($f,[ref]$null,[ref]$e)|Out-Null
if($e){"OUTER PARSE ERRORS:";$e|%{$_.Message}}else{"OUTER PARSE OK"}
# Compile SC (arm) with FocusAndConfirm + dry-run focus on my own window
Add-Type @'
using System;using System.Text;using System.Runtime.InteropServices;
public class SCchk {
  [DllImport("user32.dll")] public static extern bool SetForegroundWindow(IntPtr h);
  [DllImport("user32.dll")] public static extern bool ShowWindow(IntPtr h, int n);
  [DllImport("user32.dll")] public static extern bool BringWindowToTop(IntPtr h);
  [DllImport("user32.dll")] public static extern bool IsIconic(IntPtr h);
  [DllImport("user32.dll")] public static extern IntPtr GetForegroundWindow();
  [DllImport("user32.dll")] static extern bool EnumWindows(EnumWindowsProc cb, IntPtr l);
  [DllImport("user32.dll")] static extern uint GetWindowThreadProcessId(IntPtr h, out uint pid);
  [DllImport("user32.dll")] static extern bool IsWindowVisible(IntPtr h);
  [DllImport("user32.dll")] static extern IntPtr GetWindow(IntPtr h, uint cmd);
  [DllImport("user32.dll")] static extern int GetWindowTextLength(IntPtr h);
  [DllImport("user32.dll")] static extern bool AttachThreadInput(uint a, uint b, bool f);
  [DllImport("kernel32.dll")] static extern uint GetCurrentThreadId();
  delegate bool EnumWindowsProc(IntPtr h, IntPtr l);
  const uint GW_OWNER=4;
  public static IntPtr FindForPid(uint t){IntPtr f=IntPtr.Zero;EnumWindows((h,l)=>{uint p;GetWindowThreadProcessId(h,out p);if(p==t&&IsWindowVisible(h)&&GetWindow(h,GW_OWNER)==IntPtr.Zero&&GetWindowTextLength(h)>0){f=h;return false;}return true;},IntPtr.Zero);return f;}
  public static bool FocusAndConfirm(IntPtr h){IntPtr fg=GetForegroundWindow();uint d;uint ft=GetWindowThreadProcessId(fg,out d);uint mt=GetCurrentThreadId();bool a=(ft!=mt)&&AttachThreadInput(mt,ft,true);if(IsIconic(h))ShowWindow(h,9);BringWindowToTop(h);SetForegroundWindow(h);bool ok=(GetForegroundWindow()==h);if(a)AttachThreadInput(mt,ft,false);return ok;}
}
'@
"SC COMPILE OK"
$cur=$PID;$hwnd=[IntPtr]::Zero;$winpid=0
for($i=0;$i -lt 8;$i++){$ci=Get-CimInstance Win32_Process -Filter "ProcessId=$cur" -EA SilentlyContinue;if(-not $ci){break};$c=[SCchk]::FindForPid([uint32]$cur);if($c -ne [IntPtr]::Zero){$hwnd=$c;$winpid=$cur;break};$cur=$ci.ParentProcessId}
"TARGET: winpid=$winpid hwnd=$([int64]$hwnd)"
$r=[SCchk]::FocusAndConfirm($hwnd)
"FocusAndConfirm=$r  foreground-now=$([int64][SCchk]::GetForegroundWindow()) target=$([int64]$hwnd)"

Parse + compile + dry-run FocusAndConfirm


OUTER PARSE OK
SC COMPILE OK
TARGET: winpid=25744 hwnd=1839962
FocusAndConfirm=True  foreground-now=1839962 target=1839962


Validated: parse OK, compiles, `FocusAndConfirm=True`, foreground==target (25744/1839962).

## Fixed — root cause was a TOCTOU race

Old flow: `AppActivate` only **flashed** the window forward (background process can't hold foreground). Guard read `GetForegroundWindow==target` during that flash → passed → focus snapped back to your window → `SendKeys` delivered to **your** window. SendKeys always hits foreground *at send time*, not check time.

New flow:
- **`AttachThreadInput`** attaches our input queue to the current foreground thread → `SetForegroundWindow` actually **holds**.
- **Hard-verify** target genuinely holds foreground; **zero-gap re-check** right before send.
- **`/clear{ENTER}` in one SendKeys** (fewer race points).
- If steal can't hold → **abort, zero keys sent**. Same in the waker.

Key property now: **if focus can't be reliably taken, NOTHING is typed.** Stray keys into your active window = impossible. Worst case = clean no-op (agent stays as-is).

Didn't auto-run — can't reproduce the cross-window case from my own session (my window is already focusable here), and running it just wipes me on the happy path. **Real test:** have another agent self-clear while you sit in a *different* window. Expected: either its WT window snaps forward + clears cleanly, or it aborts with `Could not hold the agent's terminal window … NOT clearing (no keys sent)` — **never** types into your window.

Caveat: `AttachThreadInput` is the standard focus-steal; if Windows blocks it for a fully-detached agent, you get the safe abort, not a leak. Report which you see and I'll tune.


test now


<owl_reachability_notice>
You have an active owl perch. When spawning subagents or doing work, use run_in_background: true so you stay reachable for incoming messages.
</owl_reachability_notice>


CAVEMAN MODE ACTIVE (full). Drop articles/filler/pleasantries/hedging. Fragments OK. Code/commits/security: write normal.


/clear


Running live. Wipes my context; waker re-focuses + types wake note. Ending turn right after so queued `/clear` fires.


Run self-clear live test


SELF-CLEAR armed: id=todlando winpid=25744 hwnd=1839962 title="Administrator: ⠐ Deploy and bump version" — focus held + /clear queued; self-note typed in ~200ms (waker aborts unless our window holds foreground)


Proceed with next steps


Armed: focus held on my window (25744/1839962), `/clear` queued, waker spawned. Ending turn now so `/clear` fires — wake note types in ~200ms only if my window still holds foreground.