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. Focus the terminal window ---
# We must focus the window that belongs to THIS session's ancestor pid, NOT the
# program's "main window". Get-Process.MainWindowHandle returns whichever window
# the OS treats as a process's primary one — for a multi-window terminal host
# (e.g. one WindowsTerminal.exe serving several windows) that is the FIRST window,
# which is usually the wrong one. Instead, walk the parent chain and, for each
# ancestor pid, enumerate top-level windows and pick the visible, titled one OWNED
# by that exact pid (FindForPid). The deepest such ancestor is this session's window.
Add-Type @'
using System;
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")] 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);
  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;
  }
}
'@
$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
}

# 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. Focus the calling agent's terminal window (walk parents until one owns a window).
#   3. Type "/clear" + Enter  — QUEUED; fires only when this agent's turn ends.
#   4. Spawn a DETACHED waker that waits 10s, re-foregrounds the terminal, THEN
#      TYPES the self-note as a fresh user prompt + Enter.
#
# Why detached + delayed: a blocking sleep here would hold the turn open, so /clear
# could never fire. The waker must outlive the turn. The 10s 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. Focus the terminal window ---
Add-Type @'
using System;
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);
}
'@
$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 }
  $proc = Get-Process -Id $cur -ErrorAction SilentlyContinue
  if ($proc -and $proc.MainWindowHandle -ne [IntPtr]::Zero) { $hwnd = $proc.MainWindowHandle; $winpid = $cur; break }
  $cur = $ci.ParentProcessId
}
if ($hwnd -eq [IntPtr]::Zero) { Write-Error 'No ancestor window found in 8 hops.'; exit 3 }
if ([SC]::IsIconic($hwnd)) { [SC]::ShowWindow($hwnd, 9) | Out-Null }  # 9 = SW_RESTORE
[SC]::SetForegroundWindow($hwnd) | Out-Null
$ws = New-Object -ComObject WScript.Shell
try { $ws.AppActivate($winpid) | Out-Null } catch {}

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

# --- 4. Detached delayed waker (10s) — re-foregrounds + TYPES the note as a fresh prompt ---
$hptr = $hwnd.ToInt64()
$note = 'NOTE FROM THE USER: proceed with next steps.'
$inner = @"
Start-Sleep -Seconds 10
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);
}
'@
`$h = [IntPtr]$hptr
if ([SCW]::IsIconic(`$h)) { [SCW]::ShowWindow(`$h, 9) | Out-Null }
[SCW]::SetForegroundWindow(`$h) | Out-Null
`$ws = New-Object -ComObject WScript.Shell
try { `$ws.AppActivate($winpid) | Out-Null } catch {}
Start-Sleep -Milliseconds 400
`$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 — /clear queued; self-note typed in ~10s"

+# We must focus the window that belongs to THIS session's ancestor pid, NOT the

+# program's "main window". Get-Process.MainWindowHandle returns whichever window

+# the OS treats as a process's primary one — for a multi-window terminal host

+# (e.g. one WindowsTerminal.exe serving several windows) that is the FIRST window,

+# which is usually the wrong one. Instead, walk the parent chain and, for each

+# ancestor pid, enumerate top-level windows and pick the visible, titled one OWNED

+# by that exact pid (FindForPid). The deepest such ancestor is this session's window.

+  [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);

+  delegate bool EnumWindowsProc(IntPtr h, IntPtr l);

+  // First visible, titled, un-owned (top-level) window belonging to `target` pid.

+  public static IntPtr FindForPid(uint target) {

+      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

-  $proc = Get-Process -Id $cur -ErrorAction SilentlyContinue

-  if ($proc -and $proc.MainWindowHandle -ne [IntPtr]::Zero) { $hwnd = $proc.MainWindowHandle; $winpid = $cur; break }

+  $cand = [SC]::FindForPid([uint32]$cur)

+  if ($cand -ne [IntPtr]::Zero) { $hwnd = $cand; $winpid = $cur; break }


Fix 2 — 10s → 200ms (waker delay). Fix 3 — note → `Proceed with next steps`. Plus header comment + final message.


Edit

#   2. Focus the calling agent's terminal window (walk parents until one owns a window).
#   3. Type "/clear" + Enter  — QUEUED; fires only when this agent's turn ends.
#   4. Spawn a DETACHED waker that waits 10s, re-foregrounds the terminal, THEN
#      TYPES the self-note as a fresh user prompt + Enter.
#
# Why detached + delayed: a blocking sleep here would hold the turn open, so /clear
# could never fire. The waker must outlive the turn. The 10s gap lets the turn end
# and /clear wipe the context first.

#   2. Focus the calling agent's terminal window (walk parents to the ancestor pid
#      that OWNS a visible top-level window; pick that pid's window, not the
#      program's default "main" window).
#   3. Type "/clear" + Enter  — QUEUED; fires only when this agent's turn ends.
#   4. Spawn a DETACHED waker that waits 200ms, re-foregrounds the terminal, THEN
#      TYPES the self-note as a fresh user prompt + Enter.
#
# 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.


#   2. Focus the calling agent's terminal window (walk parents to the ancestor pid
#      that OWNS a visible top-level window; pick that pid's window, not the
#      program's default "main" window).
#   3. Type "/clear" + Enter  — QUEUED; fires only when this agent's turn ends.
#   4. Spawn a DETACHED waker that waits 200ms, re-foregrounds the terminal, THEN
#      TYPES the self-note as a fresh user prompt + Enter.
#
# 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.

# 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. Focus the calling agent's terminal window (walk parents until one owns a window).
#   3. Type "/clear" + Enter  — QUEUED; fires only when this agent's turn ends.
#   4. Spawn a DETACHED waker that waits 10s, re-foregrounds the terminal, THEN
#      TYPES the self-note as a fresh user prompt + Enter.
#
# Why detached + delayed: a blocking sleep here would hold the turn open, so /clear
# could never fire. The waker must outlive the turn. The 10s 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. Focus the terminal window ---
# We must focus the window that belongs to THIS session's ancestor pid, NOT the
# program's "main window". Get-Process.MainWindowHandle returns whichever window
# the OS treats as a process's primary one — for a multi-window terminal host
# (e.g. one WindowsTerminal.exe serving several windows) that is the FIRST window,
# which is usually the wrong one. Instead, walk the parent chain and, for each
# ancestor pid, enumerate top-level windows and pick the visible, titled one OWNED
# by that exact pid (FindForPid). The deepest such ancestor is this session's window.
Add-Type @'
using System;
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")] 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);
  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;
  }
}
'@
$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 window found in 8 hops.'; exit 3 }
if ([SC]::IsIconic($hwnd)) { [SC]::ShowWindow($hwnd, 9) | Out-Null }  # 9 = SW_RESTORE
[SC]::SetForegroundWindow($hwnd) | Out-Null
$ws = New-Object -ComObject WScript.Shell
try { $ws.AppActivate($winpid) | Out-Null } catch {}

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

# --- 4. Detached delayed waker (10s) — re-foregrounds + TYPES the note as a fresh prompt ---
$hptr = $hwnd.ToInt64()
$note = 'NOTE FROM THE USER: proceed with next steps.'
$inner = @"
Start-Sleep -Seconds 10
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);
}
'@
`$h = [IntPtr]$hptr
if ([SCW]::IsIconic(`$h)) { [SCW]::ShowWindow(`$h, 9) | Out-Null }
[SCW]::SetForegroundWindow(`$h) | Out-Null
`$ws = New-Object -ComObject WScript.Shell
try { `$ws.AppActivate($winpid) | Out-Null } catch {}
Start-Sleep -Milliseconds 400
`$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 — /clear queued; self-note typed in ~10s"

-#   2. Focus the calling agent's terminal window (walk parents until one owns a window).

+#   2. Focus the calling agent's terminal window (walk parents to the ancestor pid

+#      that OWNS a visible top-level window; pick that pid's window, not the

+#      program's default "main" window).

-#   4. Spawn a DETACHED waker that waits 10s, re-foregrounds the terminal, THEN

+#   4. Spawn a DETACHED waker that waits 200ms, re-foregrounds the terminal, THEN

-# could never fire. The waker must outlive the turn. The 10s gap lets the turn end

+# could never fire. The waker must outlive the turn. The short gap lets the turn end


please fix the `/self-clear` skill: -it isn't bringing the correct window into focus. it seems to focus the first window of whichever program it targets, instead of actually focusing the appropriate p…


# --- 4. Detached delayed waker (10s) — re-foregrounds + TYPES the note as a fresh prompt ---
$hptr = $hwnd.ToInt64()
$note = 'NOTE FROM THE USER: proceed with next steps.'
$inner = @"
Start-Sleep -Seconds 10

# --- 4. Detached delayed waker (200ms) — re-foregrounds + TYPES the note as a fresh prompt ---
$hptr = $hwnd.ToInt64()
$note = 'Proceed with next steps'
$inner = @"
Start-Sleep -Milliseconds 200


# --- 4. Detached delayed waker (200ms) — re-foregrounds + TYPES the note as a fresh prompt ---
$hptr = $hwnd.ToInt64()
$note = 'Proceed with next steps'
$inner = @"
Start-Sleep -Milliseconds 200

# 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. Focus the calling agent's terminal window (walk parents to the ancestor pid
#      that OWNS a visible top-level window; pick that pid's window, not the
#      program's default "main" window).
#   3. Type "/clear" + Enter  — QUEUED; fires only when this agent's turn ends.
#   4. Spawn a DETACHED waker that waits 200ms, re-foregrounds the terminal, THEN
#      TYPES the self-note as a fresh user prompt + Enter.
#
# 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. Focus the terminal window ---
# We must focus the window that belongs to THIS session's ancestor pid, NOT the
# program's "main window". Get-Process.MainWindowHandle returns whichever window
# the OS treats as a process's primary one — for a multi-window terminal host
# (e.g. one WindowsTerminal.exe serving several windows) that is the FIRST window,
# which is usually the wrong one. Instead, walk the parent chain and, for each
# ancestor pid, enumerate top-level windows and pick the visible, titled one OWNED
# by that exact pid (FindForPid). The deepest such ancestor is this session's window.
Add-Type @'
using System;
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")] 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);
  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;
  }
}
'@
$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 window found in 8 hops.'; exit 3 }
if ([SC]::IsIconic($hwnd)) { [SC]::ShowWindow($hwnd, 9) | Out-Null }  # 9 = SW_RESTORE
[SC]::SetForegroundWindow($hwnd) | Out-Null
$ws = New-Object -ComObject WScript.Shell
try { $ws.AppActivate($winpid) | Out-Null } catch {}

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

# --- 4. Detached delayed waker (10s) — re-foregrounds + TYPES the note as a fresh prompt ---
$hptr = $hwnd.ToInt64()
$note = 'NOTE FROM THE USER: proceed with next steps.'
$inner = @"
Start-Sleep -Seconds 10
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);
}
'@
`$h = [IntPtr]$hptr
if ([SCW]::IsIconic(`$h)) { [SCW]::ShowWindow(`$h, 9) | Out-Null }
[SCW]::SetForegroundWindow(`$h) | Out-Null
`$ws = New-Object -ComObject WScript.Shell
try { `$ws.AppActivate($winpid) | Out-Null } catch {}
Start-Sleep -Milliseconds 400
`$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 — /clear queued; self-note typed in ~10s"

-# --- 4. Detached delayed waker (10s) — re-foregrounds + TYPES the note as a fresh prompt ---

+# --- 4. Detached delayed waker (200ms) — re-foregrounds + TYPES the note as a fresh prompt ---

-$note = 'NOTE FROM THE USER: proceed with next steps.'


"SELF-CLEAR armed: id=$id winpid=$winpid hwnd=$hwnd — /clear queued; self-note typed in ~10s"

"SELF-CLEAR armed: id=$id winpid=$winpid hwnd=$hwnd — /clear queued; self-note typed in ~200ms"


# 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. Focus the calling agent's terminal window (walk parents to the ancestor pid
#      that OWNS a visible top-level window; pick that pid's window, not the
#      program's default "main" window).
#   3. Type "/clear" + Enter  — QUEUED; fires only when this agent's turn ends.
#   4. Spawn a DETACHED waker that waits 200ms, re-foregrounds the terminal, THEN
#      TYPES the self-note as a fresh user prompt + Enter.
#
# 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. Focus the terminal window ---
# We must focus the window that belongs to THIS session's ancestor pid, NOT the
# program's "main window". Get-Process.MainWindowHandle returns whichever window
# the OS treats as a process's primary one — for a multi-window terminal host
# (e.g. one WindowsTerminal.exe serving several windows) that is the FIRST window,
# which is usually the wrong one. Instead, walk the parent chain and, for each
# ancestor pid, enumerate top-level windows and pick the visible, titled one OWNED
# by that exact pid (FindForPid). The deepest such ancestor is this session's window.
Add-Type @'
using System;
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")] 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);
  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;
  }
}
'@
$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 window found in 8 hops.'; exit 3 }
if ([SC]::IsIconic($hwnd)) { [SC]::ShowWindow($hwnd, 9) | Out-Null }  # 9 = SW_RESTORE
[SC]::SetForegroundWindow($hwnd) | Out-Null
$ws = New-Object -ComObject WScript.Shell
try { $ws.AppActivate($winpid) | Out-Null } catch {}

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

# --- 4. Detached delayed waker (200ms) — re-foregrounds + TYPES the note as a fresh prompt ---
$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);
}
'@
`$h = [IntPtr]$hptr
if ([SCW]::IsIconic(`$h)) { [SCW]::ShowWindow(`$h, 9) | Out-Null }
[SCW]::SetForegroundWindow(`$h) | Out-Null
`$ws = New-Object -ComObject WScript.Shell
try { `$ws.AppActivate($winpid) | Out-Null } catch {}
Start-Sleep -Milliseconds 400
`$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 — /clear queued; self-note typed in ~10s"

-"SELF-CLEAR armed: id=$id winpid=$winpid hwnd=$hwnd — /clear queued; self-note typed in ~10s"

+"SELF-CLEAR armed: id=$id winpid=$winpid hwnd=$hwnd — /clear queued; self-note typed in ~200ms"


Now SKILL.md doc updates.


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

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.


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)

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.

---
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 ~10s 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 ~10s`

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 that owns the window, foregrounds it.
3. Types `/clear` + Enter — **queued**; only fires when this turn ends.
4. Spawns a **detached** waker: waits 10s (past turn-end + wipe), re-foregrounds the terminal via the arm-time `hwnd`/`winpid`, then **types** `NOTE FROM THE USER: proceed with next steps.` + Enter as a fresh user prompt.

The 10s 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

- Window focus brings the whole terminal forward; it can't target a specific tab. Clean when the agent's tab is active.
- 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 ~10s gap.
- SendKeys reserves `+ ^ % ~ ( ) { } [ ]`; the default note text uses none. If you customize the note, escape those chars.
- If your turn runs longer than ~10s after the script returns, the prompt could be typed before the wipe completes. Stop promptly after running.

-Wipes THIS agent's conversation (`/clear`), then ~10s later TYPES a self-note

+Wipes THIS agent's conversation (`/clear`), then ~200ms later TYPES a self-note


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

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


---
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 ~10s`

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 that owns the window, foregrounds it.
3. Types `/clear` + Enter — **queued**; only fires when this turn ends.
4. Spawns a **detached** waker: waits 10s (past turn-end + wipe), re-foregrounds the terminal via the arm-time `hwnd`/`winpid`, then **types** `NOTE FROM THE USER: proceed with next steps.` + Enter as a fresh user prompt.

The 10s 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

- Window focus brings the whole terminal forward; it can't target a specific tab. Clean when the agent's tab is active.
- 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 ~10s gap.
- SendKeys reserves `+ ^ % ~ ( ) { } [ ]`; the default note text uses none. If you customize the note, escape those chars.
- If your turn runs longer than ~10s after the script returns, the prompt could be typed before the wipe completes. Stop promptly after running.

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

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


2. Walks the parent chain (`pwsh → claude.exe → … → WindowsTerminal`) to the ancestor that owns the window, foregrounds it.
3. Types `/clear` + Enter — **queued**; only fires when this turn ends.
4. Spawns a **detached** waker: waits 10s (past turn-end + wipe), re-foregrounds the terminal via the arm-time `hwnd`/`winpid`, then **types** `NOTE FROM THE USER: proceed with next steps.` + Enter as a fresh user prompt.

The 10s 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.

2. Walks the parent chain (`pwsh → claude.exe → … → WindowsTerminal`) to the ancestor **pid that owns a visible top-level window** and foregrounds that pid's window (enumerated by owner pid — not the program's default "main window", which is the wrong one when one host process serves several windows).
3. Types `/clear` + Enter — **queued**; only fires when this turn ends.
4. Spawns a **detached** waker: waits 200ms (past turn-end + wipe), re-foregrounds the terminal via the arm-time `hwnd`/`winpid`, then **types** `Proceed with next steps` + Enter as a fresh user prompt.

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.


2. Walks the parent chain (`pwsh → claude.exe → … → WindowsTerminal`) to the ancestor **pid that owns a visible top-level window** and foregrounds that pid's window (enumerated by owner pid — not the program's default "main window", which is the wrong one when one host process serves several windows).
3. Types `/clear` + Enter — **queued**; only fires when this turn ends.
4. Spawns a **detached** waker: waits 200ms (past turn-end + wipe), re-foregrounds the terminal via the arm-time `hwnd`/`winpid`, then **types** `Proceed with next steps` + Enter as a fresh user prompt.

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.

---
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 that owns the window, foregrounds it.
3. Types `/clear` + Enter — **queued**; only fires when this turn ends.
4. Spawns a **detached** waker: waits 10s (past turn-end + wipe), re-foregrounds the terminal via the arm-time `hwnd`/`winpid`, then **types** `NOTE FROM THE USER: proceed with next steps.` + Enter as a fresh user prompt.

The 10s 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

- Window focus brings the whole terminal forward; it can't target a specific tab. Clean when the agent's tab is active.
- 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 ~10s gap.
- SendKeys reserves `+ ^ % ~ ( ) { } [ ]`; the default note text uses none. If you customize the note, escape those chars.
- If your turn runs longer than ~10s after the script returns, the prompt could be typed before the wipe completes. Stop promptly after running.

-2. Walks the parent chain (`pwsh → claude.exe → … → WindowsTerminal`) to the ancestor that owns the window, foregrounds it.

+2. Walks the parent chain (`pwsh → claude.exe → … → WindowsTerminal`) to the ancestor **pid that owns a visible top-level window** and foregrounds that pid's window (enumerated by owner pid — not the program's default "main window", which is the wrong one when one host process serves several windows).

-4. Spawns a **detached** waker: waits 10s (past turn-end + wipe), re-foregrounds the terminal via the arm-time `hwnd`/`winpid`, then **types** `NOTE FROM THE USER: proceed with next steps.` + Enter as a fresh user prompt.

+4. Spawns a **detached** waker: waits 200ms (past turn-end + wipe), re-foregrounds the terminal via the arm-time `hwnd`/`winpid`, then **types** `Proceed with next steps` + Enter as a fresh user prompt.

-The 10s delay + detachment are deliberate: the prompt must be typed **after**

+The delay + detachment are deliberate: the prompt must be typed **after**


- Window focus brings the whole terminal forward; it can't target a specific tab. Clean when the agent's tab is active.
- 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 ~10s gap.
- SendKeys reserves `+ ^ % ~ ( ) { } [ ]`; the default note text uses none. If you customize the note, escape those chars.
- If your turn runs longer than ~10s after the script returns, the prompt could be typed before the wipe completes. Stop promptly after running.

- 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 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.


- 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 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.

---
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** and foregrounds that pid's window (enumerated by owner pid — not the program's default "main window", which is the wrong one when one host process serves several windows).
3. Types `/clear` + Enter — **queued**; only fires when this turn ends.
4. Spawns a **detached** waker: waits 200ms (past turn-end + wipe), re-foregrounds the terminal via the arm-time `hwnd`/`winpid`, then **types** `Proceed with next steps` + Enter as a fresh user prompt.

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

- Window focus brings the whole terminal forward; it can't target a specific tab. Clean when the agent's tab is active.
- 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 ~10s gap.
- SendKeys reserves `+ ^ % ~ ( ) { } [ ]`; the default note text uses none. If you customize the note, escape those chars.
- If your turn runs longer than ~10s after the script returns, the prompt could be typed before the wipe completes. Stop promptly after running.

-- Window focus brings the whole terminal forward; it can't target a specific tab. Clean when the agent's tab is active.

-- 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 ~10s gap.

+- 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 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.

-- If your turn runs longer than ~10s after the script returns, the prompt could be typed before the wipe completes. Stop promptly after running.

+- 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.


Validating: parse-check the script (do NOT execute — would self-clear) + compile-test the C# class in isolation.


PowerShell

$f='C:\Users\decid\.claude\skills\self-clear\scripts\self-clear.ps1'
$errs=$null;$toks=$null
[System.Management.Automation.Language.Parser]::ParseFile($f,[ref]$toks,[ref]$errs)|Out-Null
if($errs){"PARSE ERRORS:";$errs|ForEach-Object{$_.Message}}else{"PARSE OK"}
# Compile-test the SC class standalone (harmless; just verifies the C#/PInvoke)
$src=@'
using System;
using System.Runtime.InteropServices;
public class SCtest {
  [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);
  delegate bool EnumWindowsProc(IntPtr h, IntPtr l);
  const uint GW_OWNER = 4;
  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;
      }
      return true;
    }, IntPtr.Zero);
    return found;
  }
}
'@
try{Add-Type $src -ErrorAction Stop;"CSHARP COMPILE OK"}catch{"CSHARP ERROR: $($_.Exception.Message)"}

Parse-check script + compile-test C# class


PARSE OK
CSHARP COMPILE OK


Both clean. Verifying the walk actually resolves a real window from this process chain (resolution only — no focus steal, no SendKeys).


$cur=$PID;$hwnd=[IntPtr]::Zero;$winpid=0;$chain=@()
for($i=0;$i -lt 8;$i++){
  $ci=Get-CimInstance Win32_Process -Filter "ProcessId=$cur" -ErrorAction SilentlyContinue
  if(-not $ci){break}
  $nm=(Get-Process -Id $cur -ErrorAction SilentlyContinue).ProcessName
  $cand=[SCtest]::FindForPid([uint32]$cur)
  $chain+="pid=$cur ($nm) win=$([int64]$cand)"
  if($cand -ne [IntPtr]::Zero){$hwnd=$cand;$winpid=$cur;break}
  $cur=$ci.ParentProcessId
}
"CHAIN:";$chain|ForEach-Object{"  $_"}
"RESOLVED: winpid=$winpid hwnd=$([int64]$hwnd)"

Verify ancestor-window resolution from current chain


InvalidOperation:
Line |
   6 |    $cand=[SCtest]::FindForPid([uint32]$cur)
     |          ~~~~~~~~
     | Unable to find type [SCtest].
CHAIN:
  pid=25120 (pwsh) win=0
RESOLVED: winpid=25120 hwnd=0


Type didn't persist (fresh shell per call). Combining compile + walk in one invocation.


$src=@'
using System;
using System.Runtime.InteropServices;
public class SCv {
  [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);
  delegate bool EnumWindowsProc(IntPtr h, IntPtr l);
  const uint GW_OWNER = 4;
  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; }
      return true;
    }, IntPtr.Zero);
    return found;
  }
}
'@
Add-Type $src
$cur=$PID;$hwnd=[IntPtr]::Zero;$winpid=0;$chain=@()
for($i=0;$i -lt 8;$i++){
  $ci=Get-CimInstance Win32_Process -Filter "ProcessId=$cur" -ErrorAction SilentlyContinue
  if(-not $ci){break}
  $nm=(Get-Process -Id $cur -ErrorAction SilentlyContinue).ProcessName
  $cand=[SCv]::FindForPid([uint32]$cur)
  $chain+="pid=$cur ($nm) win=$([int64]$cand)"
  if($cand -ne [IntPtr]::Zero){$hwnd=$cand;$winpid=$cur;break}
  $cur=$ci.ParentProcessId
}
"CHAIN:";$chain|ForEach-Object{"  $_"}
"RESOLVED: winpid=$winpid hwnd=$([int64]$hwnd)"

Compile + walk ancestor chain, report resolved window


CHAIN:
  pid=53452 (pwsh) win=0
  pid=60712 (claude.exe.old.1780437838971) win=0
  pid=59876 (pwsh) win=0
  pid=25744 (WindowsTerminal) win=1839962
RESOLVED: winpid=25744 hwnd=1839962


Fix validated. Walk resolved correctly: `pwsh → claude.exe → pwsh → WindowsTerminal(25744)` → found that pid's owned top-level window `hwnd=1839962` (skipped the windowless intermediates). Parse OK, C# compiles.

## /self-clear fixed

**1. Window focus** — replaced `Get-Process.MainWindowHandle` (returns a process's *first/main* window) with `EnumWindows` keyed to the exact ancestor PID: visible + titled + top-level (un-owned) window owned by that pid. Walk now lands on the window the correct parent PID actually owns, skipping windowless intermediates.

**2. Delay** — waker `Start-Sleep -Seconds 10` → `-Milliseconds 200` (script + header + docs + armed message).

**3. 2nd SendKeys** — note `'NOTE FROM THE USER: proceed with next steps.'` → `'Proceed with next steps'` + Enter (Enter already separate SendKeys).

Files: `scripts/self-clear.ps1` + `SKILL.md`. Not in this repo (`~/.claude/skills/`), no commit/deploy needed — live immediately.

**Honest caveat:** if your terminal is **Windows Terminal**, all its windows share ONE `WindowsTerminal.exe` PID + headless ConPTY (no per-session window), so PID-keyed enumeration still can't disambiguate between multiple WT *windows* — same hard limit as before. The fix is a real improvement for **classic conhost** (each window = own `conhost.exe` with its own HWND, which `MainWindowHandle` often returned 0 for → wrong target). If you're on WT and still see mis-targeting with multiple windows open, tell me your terminal and I'll target it differently (e.g. window-title match on the session id).


yeah that's a problem. using windows terminal now. please fix. if window title doesn't match, aboard sendkeys


<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.


WT = shared PID, headless ConPTY → can't disambiguate by PID. Right fix: capture `GetForegroundWindow()` at arm time (the specific WT window HWND the user is in) + its title; verify it's an ancestor-owned window before clearing; waker re-focuses that exact HWND and **aborts SendKeys if foreground≠target or title changed**.

Re-reading current file to craft exact edits.


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. Focus the calling agent's terminal window (walk parents to the ancestor pid
6	#      that OWNS a visible top-level window; pick that pid's window, not the
7	#      program's default "main" window).
8	#   3. Type "/clear" + Enter  — QUEUED; fires only when this agent's turn ends.
9	#   4. Spawn a DETACHED waker that waits 200ms, re-foregrounds the terminal, THEN
10	#      TYPES the self-note as a fresh user prompt + Enter.
11	#
12	# Why detached + delayed: a blocking sleep here would hold the turn open, so /clear
13	# could never fire. The waker must outlive the turn. The short gap lets the turn end
14	# and /clear wipe the context first.
15	#
16	# Why TYPE the note (not `owl send`): after /clear the session sits IDLE awaiting user
17	# input. A spooled owl message cannot self-start a turn — it surfaces only during an
18	# already-active turn (PreToolUse hook) or in a live Monitor stream (whose buffer /clear
19	# wipes). So the prior "arrives as a waking poll message" design never woke the agent.
20	# A typed prompt + Enter deterministically starts a fresh turn AND is the re-seed.
21	$ErrorActionPreference = 'Stop'
22
23	# --- 1. Resolve agent_id ---
24	$owl = $env:OWL
25	if (-not $owl) { Write-Error 'OWL env not set — restart the session so the SessionStart hook runs.'; exit 1 }
26	$who = & $owl whoami 2>&1 | Out-String
27	if ($who -notmatch 'WHOAMI:([^\s]+)') {
28	  Write-Error "No perch for this session (whoami: $($who.Trim())). Run /spt:ready or /spt:live first — NOT clearing."
29	  exit 2
30	}
31	$id = $matches[1]
32
33	# --- 2. Focus the terminal window ---
34	# We must focus the window that belongs to THIS session's ancestor pid, NOT the
35	# program's "main window". Get-Process.MainWindowHandle returns whichever window
36	# the OS treats as a process's primary one — for a multi-window terminal host
37	# (e.g. one WindowsTerminal.exe serving several windows) that is the FIRST window,
38	# which is usually the wrong one. Instead, walk the parent chain and, for each
39	# ancestor pid, enumerate top-level windows and pick the visible, titled one OWNED
40	# by that exact pid (FindForPid). The deepest such ancestor is this session's window.
41	Add-Type @'
42	using System;
43	using System.Runtime.InteropServices;
44	public class SC {
45	  [DllImport("user32.dll")] public static extern bool SetForegroundWindow(IntPtr h);
46	  [DllImport("user32.dll")] public static extern bool ShowWindow(IntPtr h, int n);
47	  [DllImport("user32.dll")] public static extern bool IsIconic(IntPtr h);
48	  [DllImport("user32.dll")] static extern bool EnumWindows(EnumWindowsProc cb, IntPtr l);
49	  [DllImport("user32.dll")] static extern uint GetWindowThreadProcessId(IntPtr h, out uint pid);
50	  [DllImport("user32.dll")] static extern bool IsWindowVisible(IntPtr h);
51	  [DllImport("user32.dll")] static extern IntPtr GetWindow(IntPtr h, uint cmd);
52	  [DllImport("user32.dll")] static extern int GetWindowTextLength(IntPtr h);
53	  delegate bool EnumWindowsProc(IntPtr h, IntPtr l);
54	  const uint GW_OWNER = 4;
55	  // First visible, titled, un-owned (top-level) window belonging to `target` pid.
56	  public static IntPtr FindForPid(uint target) {
57	    IntPtr found = IntPtr.Zero;
58	    EnumWindows((h, l) => {
59	      uint pid; GetWindowThreadProcessId(h, out pid);
60	      if (pid == target && IsWindowVisible(h) && GetWindow(h, GW_OWNER) == IntPtr.Zero && GetWindowTextLength(h) > 0) {
61	        found = h; return false; // stop enumeration
62	      }
63	      return true;
64	    }, IntPtr.Zero);
65	    return found;
66	  }
67	}
68	'@
69	$cur = $PID; $hwnd = [IntPtr]::Zero; $winpid = 0
70	for ($i = 0; $i -lt 8; $i++) {
71	  $ci = Get-CimInstance Win32_Process -Filter "ProcessId=$cur" -ErrorAction SilentlyContinue
72	  if (-not $ci) { break }
73	  $cand = [SC]::FindForPid([uint32]$cur)
74	  if ($cand -ne [IntPtr]::Zero) { $hwnd = $cand; $winpid = $cur; break }
75	  $cur = $ci.ParentProcessId
76	}
77	if ($hwnd -eq [IntPtr]::Zero) { Write-Error 'No ancestor window found in 8 hops.'; exit 3 }
78	if ([SC]::IsIconic($hwnd)) { [SC]::ShowWindow($hwnd, 9) | Out-Null }  # 9 = SW_RESTORE
79	[SC]::SetForegroundWindow($hwnd) | Out-Null
80	$ws = New-Object -ComObject WScript.Shell
81	try { $ws.AppActivate($winpid) | Out-Null } catch {}
82
83	# --- 3. Type /clear + Enter (queued; processes at turn-end) ---
84	Start-Sleep -Milliseconds 400
85	$ws.SendKeys('/clear')
86	Start-Sleep -Milliseconds 150
87	$ws.SendKeys('{ENTER}')
88
89	# --- 4. Detached delayed waker (200ms) — re-foregrounds + TYPES the note as a fresh prompt ---
90	$hptr = $hwnd.ToInt64()
91	$note = 'Proceed with next steps'
92	$inner = @"
93	Start-Sleep -Milliseconds 200
94	Add-Type @'
95	using System;
96	using System.Runtime.InteropServices;
97	public class SCW {
98	  [DllImport("user32.dll")] public static extern bool SetForegroundWindow(IntPtr h);
99	  [DllImport("user32.dll")] public static extern bool ShowWindow(IntPtr h, int n);
100	  [DllImport("user32.dll")] public static extern bool IsIconic(IntPtr h);
101	}
102	'@
103	`$h = [IntPtr]$hptr
104	if ([SCW]::IsIconic(`$h)) { [SCW]::ShowWindow(`$h, 9) | Out-Null }
105	[SCW]::SetForegroundWindow(`$h) | Out-Null
106	`$ws = New-Object -ComObject WScript.Shell
107	try { `$ws.AppActivate($winpid) | Out-Null } catch {}
108	Start-Sleep -Milliseconds 400
109	`$ws.SendKeys('$note')
110	Start-Sleep -Milliseconds 150
111	`$ws.SendKeys('{ENTER}')
112	"@
113	$enc = [Convert]::ToBase64String([Text.Encoding]::Unicode.GetBytes($inner))
114	Start-Process -FilePath 'pwsh' -ArgumentList '-NoProfile','-NonInteractive','-EncodedCommand',$enc -WindowStyle Hidden | Out-Null
115
116	"SELF-CLEAR armed: id=$id winpid=$winpid hwnd=$hwnd — /clear queued; self-note typed in ~200ms"
117

# 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. Focus the calling agent's terminal window (walk parents to the ancestor pid
#      that OWNS a visible top-level window; pick that pid's window, not the
#      program's default "main" window).
#   3. Type "/clear" + Enter  — QUEUED; fires only when this agent's turn ends.
#   4. Spawn a DETACHED waker that waits 200ms, re-foregrounds the terminal, THEN
#      TYPES the self-note as a fresh user prompt + Enter.
#
# 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. Focus the terminal window ---
# We must focus the window that belongs to THIS session's ancestor pid, NOT the
# program's "main window". Get-Process.MainWindowHandle returns whichever window
# the OS treats as a process's primary one — for a multi-window terminal host
# (e.g. one WindowsTerminal.exe serving several windows) that is the FIRST window,
# which is usually the wrong one. Instead, walk the parent chain and, for each
# ancestor pid, enumerate top-level windows and pick the visible, titled one OWNED
# by that exact pid (FindForPid). The deepest such ancestor is this session's window.
Add-Type @'
using System;
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")] 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);
  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;
  }
}
'@
$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 window found in 8 hops.'; exit 3 }
if ([SC]::IsIconic($hwnd)) { [SC]::ShowWindow($hwnd, 9) | Out-Null }  # 9 = SW_RESTORE
[SC]::SetForegroundWindow($hwnd) | Out-Null
$ws = New-Object -ComObject WScript.Shell
try { $ws.AppActivate($winpid) | Out-Null } catch {}

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

# --- 4. Detached delayed waker (200ms) — re-foregrounds + TYPES the note as a fresh prompt ---
$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);
}
'@
`$h = [IntPtr]$hptr
if ([SCW]::IsIconic(`$h)) { [SCW]::ShowWindow(`$h, 9) | Out-Null }
[SCW]::SetForegroundWindow(`$h) | Out-Null
`$ws = New-Object -ComObject WScript.Shell
try { `$ws.AppActivate($winpid) | Out-Null } catch {}
Start-Sleep -Milliseconds 400
`$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 — /clear queued; self-note typed in ~200ms"


Rewriting with foreground-window capture + title guard. Header comment first.


Edit

#   2. Focus the calling agent's terminal window (walk parents to the ancestor pid
#      that OWNS a visible top-level window; pick that pid's window, not the
#      program's default "main" window).
#   3. Type "/clear" + Enter  — QUEUED; fires only when this agent's turn ends.
#   4. Spawn a DETACHED waker that waits 200ms, re-foregrounds the terminal, THEN
#      TYPES the self-note as a fresh user prompt + Enter.

#   2. Identify this session's terminal window as the CURRENT FOREGROUND window,
#      and only proceed if that window is owned by one of this process's ancestor
#      pids (e.g. the WindowsTerminal.exe hosting this tab). Capture its HWND +
#      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-foregrounds the captured HWND, and
#      — ONLY IF that HWND is now 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 foreground + title (not pid/MainWindowHandle): Windows Terminal serves many
# windows from ONE process over headless ConPTY, so neither the ancestor pid nor
# Get-Process.MainWindowHandle can tell WT windows apart. The window the user
# triggered this from IS the foreground window at arm time; its HWND is unique. The
# title check guards the ~200ms gap — if the user switches tab/window, the title
# changes and the waker aborts instead of typing blind.


#   2. Identify this session's terminal window as the CURRENT FOREGROUND window,
#      and only proceed if that window is owned by one of this process's ancestor
#      pids (e.g. the WindowsTerminal.exe hosting this tab). Capture its HWND +
#      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-foregrounds the captured HWND, and
#      — ONLY IF that HWND is now 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 foreground + title (not pid/MainWindowHandle): Windows Terminal serves many
# windows from ONE process over headless ConPTY, so neither the ancestor pid nor
# Get-Process.MainWindowHandle can tell WT windows apart. The window the user
# triggered this from IS the foreground window at arm time; its HWND is unique. The
# title check guards the ~200ms gap — if the user switches tab/window, the title
# changes and the waker aborts instead of typing blind.

# 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. Focus the calling agent's terminal window (walk parents to the ancestor pid
#      that OWNS a visible top-level window; pick that pid's window, not the
#      program's default "main" window).
#   3. Type "/clear" + Enter  — QUEUED; fires only when this agent's turn ends.
#   4. Spawn a DETACHED waker that waits 200ms, re-foregrounds the terminal, THEN
#      TYPES the self-note as a fresh user prompt + Enter.
#
# 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. Focus the terminal window ---
# We must focus the window that belongs to THIS session's ancestor pid, NOT the
# program's "main window". Get-Process.MainWindowHandle returns whichever window
# the OS treats as a process's primary one — for a multi-window terminal host
# (e.g. one WindowsTerminal.exe serving several windows) that is the FIRST window,
# which is usually the wrong one. Instead, walk the parent chain and, for each
# ancestor pid, enumerate top-level windows and pick the visible, titled one OWNED
# by that exact pid (FindForPid). The deepest such ancestor is this session's window.
Add-Type @'
using System;
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")] 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);
  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;
  }
}
'@
$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 window found in 8 hops.'; exit 3 }
if ([SC]::IsIconic($hwnd)) { [SC]::ShowWindow($hwnd, 9) | Out-Null }  # 9 = SW_RESTORE
[SC]::SetForegroundWindow($hwnd) | Out-Null
$ws = New-Object -ComObject WScript.Shell
try { $ws.AppActivate($winpid) | Out-Null } catch {}

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

# --- 4. Detached delayed waker (200ms) — re-foregrounds + TYPES the note as a fresh prompt ---
$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);
}
'@
`$h = [IntPtr]$hptr
if ([SCW]::IsIconic(`$h)) { [SCW]::ShowWindow(`$h, 9) | Out-Null }
[SCW]::SetForegroundWindow(`$h) | Out-Null
`$ws = New-Object -ComObject WScript.Shell
try { `$ws.AppActivate($winpid) | Out-Null } catch {}
Start-Sleep -Milliseconds 400
`$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 — /clear queued; self-note typed in ~200ms"

-#   2. Focus the calling agent's terminal window (walk parents to the ancestor pid

-#      that OWNS a visible top-level window; pick that pid's window, not the

-#      program's default "main" window).

+#   2. Identify this session's terminal window as the CURRENT FOREGROUND window,

+#      and only proceed if that window is owned by one of this process's ancestor

+#      pids (e.g. the WindowsTerminal.exe hosting this tab). Capture its HWND +

+#      title bar text for the waker's safety check.

-#   4. Spawn a DETACHED waker that waits 200ms, re-foregrounds the terminal, THEN

-#      TYPES the self-note as a fresh user prompt + Enter.

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

+#      — ONLY IF that HWND is now 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 foreground + title (not pid/MainWindowHandle): Windows Terminal serves many

+# windows from ONE process over headless ConPTY, so neither the ancestor pid nor

+# Get-Process.MainWindowHandle can tell WT windows apart. The window the user

+# triggered this from IS the foreground window at arm time; its HWND is unique. The

+# title check guards the ~200ms gap — if the user switches tab/window, the title

+# changes and the waker aborts instead of typing blind.


Now the C# class + arm logic (section 2).


# --- 2. Focus the terminal window ---
# We must focus the window that belongs to THIS session's ancestor pid, NOT the
# program's "main window". Get-Process.MainWindowHandle returns whichever window
# the OS treats as a process's primary one — for a multi-window terminal host
# (e.g. one WindowsTerminal.exe serving several windows) that is the FIRST window,
# which is usually the wrong one. Instead, walk the parent chain and, for each
# ancestor pid, enumerate top-level windows and pick the visible, titled one OWNED
# by that exact pid (FindForPid). The deepest such ancestor is this session's window.
Add-Type @'
using System;
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")] 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);
  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;
  }
}
'@
$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 window found in 8 hops.'; exit 3 }
if ([SC]::IsIconic($hwnd)) { [SC]::ShowWindow($hwnd, 9) | Out-Null }  # 9 = SW_RESTORE
[SC]::SetForegroundWindow($hwnd) | Out-Null
$ws = New-Object -ComObject WScript.Shell
try { $ws.AppActivate($winpid) | Out-Null } catch {}

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

# --- 2. Identify this session's terminal window (foreground + ancestor-owned) ---
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 uint GetWindowThreadProcessId(IntPtr h, out uint pid);
  [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);
  public static uint PidOf(IntPtr h) { uint p; GetWindowThreadProcessId(h, out p); return p; }
  public static string TitleOf(IntPtr h) {
    int n = GetWindowTextLength(h);
    StringBuilder sb = new StringBuilder(n + 1);
    GetWindowText(h, sb, sb.Capacity);
    return sb.ToString();
  }
}
'@
# Collect this process's ancestor pids (self up to 8 hops).
$ancestors = @(); $cur = $PID
for ($i = 0; $i -lt 8; $i++) {
  $ancestors += [uint32]$cur
  $ci = Get-CimInstance Win32_Process -Filter "ProcessId=$cur" -ErrorAction SilentlyContinue
  if (-not $ci) { break }
  $cur = $ci.ParentProcessId
}
# The window the user triggered this from is the foreground window. Require that it
# belongs to one of our ancestor pids (the terminal hosting this tab) — otherwise we
# cannot safely identify the right window, so abort WITHOUT clearing.
$hwnd = [SC]::GetForegroundWindow()
$winpid = if ($hwnd -ne [IntPtr]::Zero) { [SC]::PidOf($hwnd) } else { 0 }
if ($hwnd -eq [IntPtr]::Zero -or ($ancestors -notcontains [uint32]$winpid)) {
  Write-Error "Foreground window (pid=$winpid) is not this agent's terminal — keep the agent's Windows Terminal window/tab focused and retry. NOT clearing."
  exit 3
}
$title = [SC]::TitleOf($hwnd)   # captured now; the waker re-checks it before typing
$ws = New-Object -ComObject WScript.Shell

# --- 3. Type /clear + Enter (queued; processes at turn-end) ---
# The window is already foreground (we just verified it), so no focus change needed.
$ws.SendKeys('/clear')
Start-Sleep -Milliseconds 150
$ws.SendKeys('{ENTER}')


# --- 2. Identify this session's terminal window (foreground + ancestor-owned) ---
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 uint GetWindowThreadProcessId(IntPtr h, out uint pid);
  [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);
  public static uint PidOf(IntPtr h) { uint p; GetWindowThreadProcessId(h, out p); return p; }
  public static string TitleOf(IntPtr h) {
    int n = GetWindowTextLength(h);
    StringBuilder sb = new StringBuilder(n + 1);
    GetWindowText(h, sb, sb.Capacity);
    return sb.ToString();
  }
}
'@
# Collect this process's ancestor pids (self up to 8 hops).
$ancestors = @(); $cur = $PID
for ($i = 0; $i -lt 8; $i++) {
  $ancestors += [uint32]$cur
  $ci = Get-CimInstance Win32_Process -Filter "ProcessId=$cur" -ErrorAction SilentlyContinue
  if (-not $ci) { break }
  $cur = $ci.ParentProcessId
}
# The window the user triggered this from is the foreground window. Require that it
# belongs to one of our ancestor pids (the terminal hosting this tab) — otherwise we
# cannot safely identify the right window, so abort WITHOUT clearing.
$hwnd = [SC]::GetForegroundWindow()
$winpid = if ($hwnd -ne [IntPtr]::Zero) { [SC]::PidOf($hwnd) } else { 0 }
if ($hwnd -eq [IntPtr]::Zero -or ($ancestors -notcontains [uint32]$winpid)) {
  Write-Error "Foreground window (pid=$winpid) is not this agent's terminal — keep the agent's Windows Terminal window/tab focused and retry. NOT clearing."
  exit 3
}
$title = [SC]::TitleOf($hwnd)   # captured now; the waker re-checks it before typing
$ws = New-Object -ComObject WScript.Shell

# --- 3. Type /clear + Enter (queued; processes at turn-end) ---
# The window is already foreground (we just verified it), so no focus change needed.
$ws.SendKeys('/clear')
Start-Sleep -Milliseconds 150
$ws.SendKeys('{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 as the CURRENT FOREGROUND window,
#      and only proceed if that window is owned by one of this process's ancestor
#      pids (e.g. the WindowsTerminal.exe hosting this tab). Capture its HWND +
#      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-foregrounds the captured HWND, and
#      — ONLY IF that HWND is now 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 foreground + title (not pid/MainWindowHandle): Windows Terminal serves many
# windows from ONE process over headless ConPTY, so neither the ancestor pid nor
# Get-Process.MainWindowHandle can tell WT windows apart. The window the user
# triggered this from IS the foreground window at arm time; its HWND is unique. The
# title check guards the ~200ms gap — if the user switches tab/window, the title
# changes and the waker aborts instead of typing blind.
#
# 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. Focus the terminal window ---
# We must focus the window that belongs to THIS session's ancestor pid, NOT the
# program's "main window". Get-Process.MainWindowHandle returns whichever window
# the OS treats as a process's primary one — for a multi-window terminal host
# (e.g. one WindowsTerminal.exe serving several windows) that is the FIRST window,
# which is usually the wrong one. Instead, walk the parent chain and, for each
# ancestor pid, enumerate top-level windows and pick the visible, titled one OWNED
# by that exact pid (FindForPid). The deepest such ancestor is this session's window.
Add-Type @'
using System;
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")] 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);
  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;
  }
}
'@
$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 window found in 8 hops.'; exit 3 }
if ([SC]::IsIconic($hwnd)) { [SC]::ShowWindow($hwnd, 9) | Out-Null }  # 9 = SW_RESTORE
[SC]::SetForegroundWindow($hwnd) | Out-Null
$ws = New-Object -ComObject WScript.Shell
try { $ws.AppActivate($winpid) | Out-Null } catch {}

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

# --- 4. Detached delayed waker (200ms) — re-foregrounds + TYPES the note as a fresh prompt ---
$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);
}
'@
`$h = [IntPtr]$hptr
if ([SCW]::IsIconic(`$h)) { [SCW]::ShowWindow(`$h, 9) | Out-Null }
[SCW]::SetForegroundWindow(`$h) | Out-Null
`$ws = New-Object -ComObject WScript.Shell
try { `$ws.AppActivate($winpid) | Out-Null } catch {}
Start-Sleep -Milliseconds 400
`$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 — /clear queued; self-note typed in ~200ms"

-# We must focus the window that belongs to THIS session's ancestor pid, NOT the

-# program's "main window". Get-Process.MainWindowHandle returns whichever window

-# the OS treats as a process's primary one — for a multi-window terminal host

-# (e.g. one WindowsTerminal.exe serving several windows) that is the FIRST window,

-# which is usually the wrong one. Instead, walk the parent chain and, for each

-# ancestor pid, enumerate top-level windows and pick the visible, titled one OWNED

-# by that exact pid (FindForPid). The deepest such ancestor is this session's window.

+# --- 2. Identify this session's terminal window (foreground + ancestor-owned) ---

-  [DllImport("user32.dll")] static extern bool EnumWindows(EnumWindowsProc cb, IntPtr l);

+  [DllImport("user32.dll")] public static extern IntPtr GetForegroundWindow();

-  [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);

-  delegate bool EnumWindowsProc(IntPtr h, IntPtr l);

-  // First visible, titled, un-owned (top-level) window belonging to `target` pid.

-  public static IntPtr FindForPid(uint target) {

-      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

+  public static uint PidOf(IntPtr h) { uint p; GetWindowThreadProcessId(h, out p); return p; }

+  public static string TitleOf(IntPtr h) {

+    StringBuilder sb = new StringBuilder(n + 1);

-$cur = $PID; $hwnd = [IntPtr]::Zero; $winpid = 0

+# Collect this process's ancestor pids (self up to 8 hops).

-  $cand = [SC]::FindForPid([uint32]$cur)

-  if ($cand -ne [IntPtr]::Zero) { $hwnd = $cand; $winpid = $cur; break }

-if ($hwnd -eq [IntPtr]::Zero) { Write-Error 'No ancestor window found in 8 hops.'; exit 3 }

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

+# The window the user triggered this from is the foreground window. Require that it

+# belongs to one of our ancestor pids (the terminal hosting this tab) — otherwise we

+# cannot safely identify the right window, so abort WITHOUT clearing.

+$winpid = if ($hwnd -ne [IntPtr]::Zero) { [SC]::PidOf($hwnd) } else { 0 }

+if ($hwnd -eq [IntPtr]::Zero -or ($ancestors -notcontains [uint32]$winpid)) {

+  Write-Error "Foreground window (pid=$winpid) is not this agent's terminal — keep the agent's Windows Terminal window/tab focused and retry. NOT clearing."

+$title = [SC]::TitleOf($hwnd)   # captured now; the waker re-checks it before typing

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

+# The window is already foreground (we just verified it), so no focus change needed.


Now the waker — add foreground + title guard, abort SendKeys on mismatch.


# --- 4. Detached delayed waker (200ms) — re-foregrounds + TYPES the note as a fresh prompt ---
$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);
}
'@
`$h = [IntPtr]$hptr
if ([SCW]::IsIconic(`$h)) { [SCW]::ShowWindow(`$h, 9) | Out-Null }
[SCW]::SetForegroundWindow(`$h) | Out-Null
`$ws = New-Object -ComObject WScript.Shell
try { `$ws.AppActivate($winpid) | Out-Null } catch {}
Start-Sleep -Milliseconds 400
`$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 — /clear queued; self-note typed in ~200ms"

# --- 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'
# Pass the captured title as base64 so arbitrary title chars can't break the inner script.
$titleB64 = [Convert]::ToBase64String([Text.Encoding]::Unicode.GetBytes($title))
$inner = @"
Start-Sleep -Milliseconds 200
Add-Type @'
using System;
using System.Text;
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();
  [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);
  public static string TitleOf(IntPtr h) { int n = GetWindowTextLength(h); StringBuilder sb = new StringBuilder(n + 1); GetWindowText(h, sb, sb.Capacity); return sb.ToString(); }
}
'@
`$h = [IntPtr]$hptr
`$want = [Text.Encoding]::Unicode.GetString([Convert]::FromBase64String('$titleB64'))
if ([SCW]::IsIconic(`$h)) { [SCW]::ShowWindow(`$h, 9) | Out-Null }
[SCW]::SetForegroundWindow(`$h) | Out-Null
Start-Sleep -Milliseconds 120
# ABORT the SendKeys unless our exact window is foreground AND its title is unchanged.
`$fg = [SCW]::GetForegroundWindow()
`$now = [SCW]::TitleOf(`$h)
if (`$fg -ne `$h -or `$now -ne `$want) { exit 0 }
`$ws = New-Object -ComObject WScript.Shell
`$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)"