feat: send text to named sessions with draft protection and listener isolation

- chat send and listener-smoke accept --session; defaults remain File Transfer Assistant.
- Reject existing drafts, reacquire the send control after input changes, and require a
  fresh message fingerprint for confirmation without retrying failed sends.
- Test-NamedTextSend sends the same text twice and verifies distinct confirmed fingerprints;
  Test-NamedListeners -SendMarkers verifies per-window own events exactly once with zero
  cross-session events. Note export now requires a unique fresh owned note window and keeps
  repeated paragraphs. 97 Core tests pass; real-machine validated.
This commit is contained in:
2026-09-06 14:35:55 +08:00
parent fb8c25c83c
commit ad22a89185
7 changed files with 166 additions and 33 deletions
+33 -5
View File
@@ -1,6 +1,7 @@
param(
[Parameter(Mandatory=$true)][string]$Executable,
[Parameter(Mandatory=$true)][string]$OutputDirectory
[Parameter(Mandatory=$true)][string]$OutputDirectory,
[switch]$SendMarkers
)
$ErrorActionPreference='Stop'
$exe=(Resolve-Path $Executable).Path
@@ -9,29 +10,56 @@ if (Test-Path $directory) { throw 'Use a new evidence directory.' }
[IO.Directory]::CreateDirectory($directory) | Out-Null
$targets=@('文件传输助手','消息测试专用群组')
$processes=@()
$sent=@()
$seconds=if($SendMarkers){45}else{8}
try {
for($i=0;$i -lt $targets.Count;$i++) {
$arguments=@('chat','monitor','--independent','--session',('"'+$targets[$i]+'"'),
'--seconds','8','--state-file',('"'+$directory+'\state-'+$i+'.json"'),'--timeout','20')
'--seconds',$seconds,'--state-file',('"'+$directory+'\state-'+$i+'.json"'),'--timeout','60')
$process=Start-Process $exe -ArgumentList $arguments -PassThru -NoNewWindow `
-RedirectStandardOutput "$directory\events-$i.jsonl" -RedirectStandardError "$directory\stderr-$i.txt"
$null=$process.Handle # Retain the process handle so PowerShell 5 can retrieve ExitCode after exit.
$processes+=$process
}
if($SendMarkers){
$readyDeadline=[DateTime]::UtcNow.AddSeconds(20)
while(!(Test-Path "$directory\state-0.json") -or !(Test-Path "$directory\state-1.json")){
if([DateTime]::UtcNow -ge $readyDeadline){throw 'Listeners did not establish both baselines; no messages sent.'}
Start-Sleep -Milliseconds 200
}
foreach($target in $targets){
$marker='wx-agent-isolation-'+[Guid]::NewGuid().ToString('N')
$message=(& $exe chat send --session $target --text $marker --timeout 30 | Out-String) | ConvertFrom-Json
$code=$LASTEXITCODE
$sent+=@{ExitCode=$code;Fingerprint=$message.Fingerprint}
if($code -ne 0){break} # Never retry a failed send.
}
}
$checks=@()
for($i=0;$i -lt $processes.Count;$i++) {
$process=$processes[$i]
if (!$process.WaitForExit(30000)) { throw 'Named listener exceeded its deadline.' }
if (!$process.WaitForExit(60000)) { throw 'Named listener exceeded its deadline.' }
$exitCode=$process.ExitCode
$statePath="$directory\state-$i.json"
$state=if(Test-Path $statePath){ Get-Content $statePath -Raw | ConvertFrom-Json }else{$null}
& $exe inspect-ui --window-title $targets[$i] --output "$directory\ui-$i.json" --timeout 20 | Out-Null
$inspectCode=$LASTEXITCODE
$checks+=@{Index=$i;ExitCode=$exitCode;CheckpointSaved=($null -ne $state);
$check=@{Index=$i;ExitCode=$exitCode;CheckpointSaved=($null -ne $state);
SessionMatches=($state.session -eq $targets[$i]);FingerprintCount=if($null -ne $state){@($state.fingerprints).Count}else{0};InspectExit=$inspectCode}
if($SendMarkers -and $sent.Count -eq 2){
$events=@(Get-Content "$directory\events-$i.jsonl" | Where-Object {$_} | ForEach-Object {$_ | ConvertFrom-Json})
$check.OwnEvents=@($events | Where-Object {$_.message.Fingerprint -eq $sent[$i].Fingerprint -and $_.Session -ceq $targets[$i]}).Count
$check.OtherSessionEvents=@($events | Where-Object {$_.message.Fingerprint -eq $sent[1-$i].Fingerprint}).Count
}
$checks+=$check
}
$passed=@($checks | Where-Object {$_.ExitCode -ne 0 -or !$_.CheckpointSaved -or !$_.SessionMatches -or $_.FingerprintCount -lt 1 -or $_.InspectExit -ne 0}).Count -eq 0
$result=@{Checks=$checks;Passed=$passed;MessagesSent=0;Scope='Parallel named-window baseline only; not incoming-message delivery or endurance.'}
if($SendMarkers){
$passed=$passed -and $sent.Count -eq 2 -and @($sent | Where-Object {$_.ExitCode -ne 0}).Count -eq 0 -and
@($checks | Where-Object {$_.OwnEvents -ne 1 -or $_.OtherSessionEvents -ne 0}).Count -eq 0
}
$result=@{Checks=$checks;Passed=$passed;AttemptedSends=$sent.Count;MessagesSent=@($sent | Where-Object {$_.ExitCode -eq 0}).Count;
Scope=if($SendMarkers){'Own sent-message events across two independent windows; not peer incoming delivery or endurance.'}else{'Parallel named-window baseline only; not incoming-message delivery or endurance.'}}
[IO.File]::WriteAllText("$directory\summary.json",($result | ConvertTo-Json -Depth 5),(New-Object Text.UTF8Encoding($false)))
if(!$passed){exit 2}
} finally {
+23
View File
@@ -0,0 +1,23 @@
param(
[Parameter(Mandatory=$true)][string]$Executable,
[Parameter(Mandatory=$true)][string]$OutputDirectory,
[string]$Session='消息测试专用群组'
)
$ErrorActionPreference='Stop'
$exe=(Resolve-Path $Executable).Path
if(Test-Path $OutputDirectory){throw 'Use a fresh evidence directory.'}
$directory=(New-Item -ItemType Directory $OutputDirectory).FullName
$marker='wx-agent-repeat-'+[Guid]::NewGuid().ToString('N')
$results=@()
for($i=0;$i -lt 2;$i++){
$result=(& $exe chat send --session $Session --text $marker --timeout 30 | Out-String) | ConvertFrom-Json
$exitCode=$LASTEXITCODE
$results+=@{ExitCode=$exitCode;Fingerprint=$result.Fingerprint;Error=$result.error;Message=$result.message}
if($exitCode -ne 0){break} # Never retry a failed send.
}
$success=($results.Count -eq 2 -and $results[0].ExitCode -eq 0 -and $results[1].ExitCode -eq 0 -and
$null -ne $results[0].Fingerprint -and $null -ne $results[1].Fingerprint -and
$results[0].Fingerprint -ne $results[1].Fingerprint)
@{Success=$success;Results=$results;AttemptedSends=$results.Count;BinarySha256=(Get-FileHash $exe -Algorithm SHA256).Hash} |
ConvertTo-Json -Depth 5 | Set-Content "$directory\summary.json" -Encoding UTF8
if(!$success){exit 1}