feat: v2.5.4 — Query Explorer folded into Sessions, Windows picker and paths

- Sessions page tabs: Sessions · Events · Agents. QueryExplorer.tsx became
  AgentsTable.tsx (agents only — sessions and events already had tabs);
  /query redirects to /sessions?view=agents and leaves the sidebar.
  GET /api/query is unchanged and still answers all three entities.
- Windows picker opens in front: -EncodedCommand script spends the hidden
  first ShowWindow that windowsHide gives the process, then brings the TopMost
  owner forward via AttachThreadInput (v2.5.3's owner was itself hidden).
- Session page Resume/Fork/Open pane: owningLane and sessionWorktreeName
  match Windows folders (either separator), with a test.
This commit is contained in:
2026-10-06 21:56:51 +07:00
parent af014d9829
commit dd9b23106c
23 changed files with 168 additions and 150 deletions
+43 -7
View File
@@ -61,6 +61,25 @@ function startDir(start) {
return os.homedir();
}
// user32 calls PowerShell has no cmdlet for: `Front` makes a window the
// foreground one from a background process (see dialogCommands).
const WIN_FRONT = [
'[DllImport("user32.dll")] public static extern bool ShowWindow(IntPtr h, int n);',
'[DllImport("user32.dll")] public static extern bool SetForegroundWindow(IntPtr h);',
'[DllImport("user32.dll")] public static extern bool BringWindowToTop(IntPtr h);',
'[DllImport("user32.dll")] public static extern IntPtr GetForegroundWindow();',
'[DllImport("user32.dll")] public static extern uint GetWindowThreadProcessId(IntPtr h, IntPtr p);',
'[DllImport("user32.dll")] public static extern bool AttachThreadInput(uint a, uint b, bool on);',
'[DllImport("kernel32.dll")] public static extern uint GetCurrentThreadId();',
"public static void Front(IntPtr h) {",
" uint fg = GetWindowThreadProcessId(GetForegroundWindow(), IntPtr.Zero);",
" uint me = GetCurrentThreadId();",
" bool joined = fg != 0 && fg != me && AttachThreadInput(me, fg, true);",
" BringWindowToTop(h); SetForegroundWindow(h);",
" if (joined) AttachThreadInput(me, fg, false);",
"}",
].join("\n");
/** The candidate dialog commands for a platform, in order of preference. */
function dialogCommands(kind, dir, platform = process.platform) {
const title = TITLES[kind];
@@ -88,19 +107,36 @@ function dialogCommands(kind, dir, platform = process.platform) {
? "$d = New-Object System.Windows.Forms.FolderBrowserDialog; $d.SelectedPath = $env:AGENTHUB_PICK_START; $d.Description = $env:AGENTHUB_PICK_TITLE"
: "$d = New-Object System.Windows.Forms.OpenFileDialog; $d.InitialDirectory = $env:AGENTHUB_PICK_START; $d.Title = $env:AGENTHUB_PICK_TITLE";
const result = kind === "dir" ? "$d.SelectedPath" : "$d.FileName";
// The server is a background process, and Windows keeps one from bringing
// a window forward: an ownerless dialog opened at the BOTTOM of the
// z-order, behind the browser. So the dialog gets a TopMost owner, made
// the foreground window by joining the current foreground thread's input
// (AttachThreadInput — the lock exempts it). The first ShowWindow of a
// process started with windowsHide is turned into a hide; a throwaway call
// spends it, or the owner stays hidden and takes the dialog down with it.
const script = [
"$ProgressPreference = 'SilentlyContinue'",
"[Console]::OutputEncoding = [Text.Encoding]::UTF8",
"Add-Type -AssemblyName System.Windows.Forms",
`Add-Type -Namespace AgentHub -Name Win -MemberDefinition '${WIN_FRONT}'`,
"$o = New-Object System.Windows.Forms.Form",
"$o.TopMost = $true; $o.ShowInTaskbar = $false; $o.FormBorderStyle = 'None'; $o.Opacity = 0; $o.Width = 1; $o.Height = 1; $o.StartPosition = 'CenterScreen'",
"[void][AgentHub.Win]::ShowWindow($o.Handle, 1)",
"$o.Show()",
"[AgentHub.Win]::Front($o.Handle)",
dialog,
`if ($d.ShowDialog($o) -eq 'OK') { [Console]::Out.Write(${result}) } else { exit 1 }`,
].join("\n");
// -EncodedCommand: the C# above carries double quotes, which Windows
// PowerShell's own command-line parsing mangles inside -Command.
return [
[
"powershell.exe",
[
"-NoProfile",
"-STA",
"-Command",
// The server is a background process, and Windows will not let one
// bring a window to the front: an ownerless dialog opened BEHIND the
// browser (or hidden — the first ShowWindow of a process started
// with windowsHide). An invisible TopMost owner, shown first, takes
// that first ShowWindow and keeps the dialog on top.
`[Console]::OutputEncoding = [Text.Encoding]::UTF8; Add-Type -AssemblyName System.Windows.Forms; $o = New-Object System.Windows.Forms.Form; $o.TopMost = $true; $o.ShowInTaskbar = $false; $o.FormBorderStyle = 'None'; $o.Opacity = 0; $o.Width = 1; $o.Height = 1; $o.Show(); $o.Activate(); ${dialog}; if ($d.ShowDialog($o) -eq 'OK') { [Console]::Out.Write(${result}) } else { exit 1 }`,
"-EncodedCommand",
Buffer.from(script, "utf16le").toString("base64"),
],
],
];