summrise-agent 1.2.491 → 1.2.493
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/bin/summrise.js +306 -28
- package/package.json +1 -1
- package/summrise-agent.exe +0 -0
- package/summrise-desktop-electron/src/main.js +8 -1
package/bin/summrise.js
CHANGED
|
@@ -39,6 +39,7 @@ exports.psArgv = psArgv;
|
|
|
39
39
|
exports.lnkIdentity = lnkIdentity;
|
|
40
40
|
exports.deskShortcutRepairPs = deskShortcutRepairPs;
|
|
41
41
|
exports.startDesktopPs = startDesktopPs;
|
|
42
|
+
exports.ensureDesktopPs = ensureDesktopPs;
|
|
42
43
|
exports.desktopTaskPs = desktopTaskPs;
|
|
43
44
|
exports.desktopStartPs = desktopStartPs;
|
|
44
45
|
exports.parseAgentPort = parseAgentPort;
|
|
@@ -310,8 +311,42 @@ exports.psq = psq;
|
|
|
310
311
|
* silently reverts to the backing executable (stock `electron.exe`, since the shell runs
|
|
311
312
|
* unpackaged) with nothing on the device to read that says why.
|
|
312
313
|
* PowerShell/Windows AppUserModelIDs cannot contain spaces, and this one does not.
|
|
314
|
+
*
|
|
315
|
+
* WHY IT IS NOT `online.saisi.summrise.agent`, WHICH IS WHAT IT WAS CALLED BEFORE —
|
|
316
|
+
* MEASURED on desktop-14rjcr8 (release 1.2.490), ONE VARIABLE AT A TIME:
|
|
317
|
+
*
|
|
318
|
+
* | window's AppUserModelID | shortcut carries it | taskbar draws |
|
|
319
|
+
* | window's AppUserModelID | shortcuts carrying it | taskbar draws |
|
|
320
|
+
* | online.saisi.summrise.agent | both desktops + Start Menu | Electron logo |
|
|
321
|
+
* | online.saisi.summrise.desktop | both desktops + Start Menu | THE SUNRISE |
|
|
322
|
+
* | online.saisi.summrise.desktop | both desktops, Start Menu GONE | Electron logo |
|
|
323
|
+
*
|
|
324
|
+
* That third row is the one that decides where the identity has to live, and it was taken
|
|
325
|
+
* WITHOUT restarting anything: the same running shell, the same AppUserModelID already set,
|
|
326
|
+
* and the icon fell back to the Electron logo the moment the Start Menu shortcut was
|
|
327
|
+
* deleted. So the shortcut this lookup resolves is the **Start Menu** one — a desktop
|
|
328
|
+
* shortcut is not enough, and writing the identity onto `Summrise.lnk` in
|
|
329
|
+
* `%PUBLIC%\Desktop` alone changes nothing. Microsoft's appids.md does say to apply the ID
|
|
330
|
+
* to "all of the shortcuts" when an application has several launch locations, and it lists
|
|
331
|
+
* the Start menu FIRST; the measurement is what says which of them the taskbar actually
|
|
332
|
+
* reads.
|
|
333
|
+
*
|
|
334
|
+
* The other variables were held: the same `IPropertyStore` call read back on every
|
|
335
|
+
* shortcut, the same `icon.ico` (downloaded and RENDERED — it is the orange sunrise), the
|
|
336
|
+
* same shell restarted under the `SummriseDesktop` task, and in each restarted run the
|
|
337
|
+
* running process appended `AUMID-SET-OK` to a log from inside `setAppUserModelId`, so "the
|
|
338
|
+
* ID was set" is measured rather than assumed.
|
|
339
|
+
*
|
|
340
|
+
* WHY THE OLD ID WAS DEAD. Releases up to 1.2.489 set `online.saisi.summrise.agent` on the
|
|
341
|
+
* process while no shortcut anywhere carried it, so the shell resolved that ID against the
|
|
342
|
+
* backing executable — stock `electron.exe` — and kept the result. `shell:AppsFolder` still
|
|
343
|
+
* lists it as `name=[Electron] path=[online.saisi.summrise.agent]`, an app entry named after
|
|
344
|
+
* `electron.exe`. An ID already resolved to the wrong image is not repaired by writing the
|
|
345
|
+
* shortcut afterwards; the association has to be under a name the machine has never seen.
|
|
346
|
+
* Keep this string STABLE from here: changing it again costs the pin and the jump list, so
|
|
347
|
+
* this is a one-time migration, not a knob.
|
|
313
348
|
*/
|
|
314
|
-
exports.DESKTOP_AUMID = "online.saisi.summrise.
|
|
349
|
+
exports.DESKTOP_AUMID = "online.saisi.summrise.desktop";
|
|
315
350
|
/** The two `IPropertyStore` values `Summrise.lnk` must carry, as DATA.
|
|
316
351
|
*
|
|
317
352
|
* Deliberately separated from the PowerShell that performs the write, and exported, so the
|
|
@@ -442,45 +477,240 @@ public static class SummriseLnk {
|
|
|
442
477
|
// exported: unit-tested in test/cli.test.mjs.
|
|
443
478
|
function deskShortcutRepairPs(scriptsQ, deskDirQ, sink) {
|
|
444
479
|
const { id, icon } = lnkIdentity(`${deskDirQ}\\icon.ico`);
|
|
480
|
+
// BOTH shortcuts get the identity, and the START MENU one is the load-bearing half:
|
|
481
|
+
// deleting it from a running shell reverted the taskbar to the Electron logo with the ID
|
|
482
|
+
// still set on the process and both desktop shortcuts still carrying it (see DESKTOP_AUMID).
|
|
483
|
+
// The desktop link is kept because it is what a person double-clicks and because
|
|
484
|
+
// appids.md says to apply the ID to "all of the shortcuts" a user can launch from.
|
|
485
|
+
const lnk = (p, tag) => [
|
|
486
|
+
` try { $dWs${tag} = New-Object -ComObject WScript.Shell; $dSc${tag} = $dWs${tag}.CreateShortcut(${p}); $dSc${tag}.TargetPath = Join-Path $env:SystemRoot 'System32\\WindowsPowerShell\\v1.0\\powershell.exe'; $dSc${tag}.Arguments = '-NoProfile -ExecutionPolicy Bypass -File "' + $dPs1 + '"'; $dSc${tag}.WorkingDirectory = '${deskDirQ}'; $dSc${tag}.IconLocation = $dIco + ',0'; $dSc${tag}.Save(); 'desk: ' + ${p} + ' repointed to electron shell' | ${sink} } catch { ('desk: ' + ${p} + ' repair failed: ' + $_.Exception.Message) | ${sink} }`,
|
|
487
|
+
` if ($dSet) { try { [SummriseLnk]::Write(${p}, $dId, $dRes); ('desk: ' + ${p} + ' taskbar identity [' + [SummriseLnk]::Read(${p}) + ']') | ${sink} } catch { ('desk: ' + ${p} + ' identity write failed: ' + $_.Exception.Message) | ${sink} } }`,
|
|
488
|
+
];
|
|
445
489
|
return [
|
|
446
490
|
`$dLnk = Join-Path $env:PUBLIC 'Desktop\\Summrise.lnk'`,
|
|
491
|
+
// ALL-USERS Start Menu: the shortcut the shell's app resolver actually reads, and it is
|
|
492
|
+
// CREATED here when absent — a machine that never had one is exactly the machine whose
|
|
493
|
+
// taskbar shows the Electron logo, and no amount of desktop-side repair reaches it.
|
|
494
|
+
// `setup` already requires elevation (HKLM writes, Program Files), so ProgramData is
|
|
495
|
+
// writable on every path that calls this.
|
|
496
|
+
`$dSm = Join-Path $env:ProgramData 'Microsoft\\Windows\\Start Menu\\Programs\\Summrise.lnk'`,
|
|
447
497
|
`$dIco = '${deskDirQ}\\icon.ico'`,
|
|
448
498
|
`$dPs1 = '${scriptsQ}\\start-desktop.ps1'`,
|
|
449
499
|
`$dId = '${id}'`,
|
|
450
500
|
`$dRes = '${icon}'`,
|
|
451
501
|
`$dSet = $false`,
|
|
452
|
-
`$
|
|
453
|
-
|
|
454
|
-
//
|
|
455
|
-
// it. The `-as [type]` guard keeps a
|
|
456
|
-
// "type already exists" throw into a silent
|
|
502
|
+
`$dShell = (Test-Path $dIco) -and (Test-Path $dPs1)`,
|
|
503
|
+
// Compiled when there is a SHELL to point at, not when a desktop link happens to exist:
|
|
504
|
+
// the Start Menu shortcut is created from nothing, so gating this on `$dLnk` would skip
|
|
505
|
+
// the identity on precisely the installs that need it. The `-as [type]` guard keeps a
|
|
506
|
+
// second call in one process from turning a "type already exists" throw into a silent
|
|
507
|
+
// skip of every write below.
|
|
508
|
+
`if ($dShell) {`,
|
|
457
509
|
` $dCs = @'\n${LNK_PROPS_CS}\n'@`,
|
|
458
510
|
` try { if (-not ('SummriseLnk' -as [type])) { Add-Type -TypeDefinition $dCs }; $dSet = [bool]('SummriseLnk' -as [type]) } catch { ('desk: lnk identity helper unavailable: ' + $_.Exception.Message) | ${sink} }`,
|
|
511
|
+
`}`,
|
|
512
|
+
`$dNeed = $false`,
|
|
513
|
+
`if (Test-Path $dLnk) {`,
|
|
459
514
|
` try { $dEx = (New-Object -ComObject WScript.Shell).CreateShortcut($dLnk); if (($dEx.TargetPath -like '*summrise-desktop.exe') -or ($dEx.TargetPath -like '*summrise-tray.exe') -or (-not (Test-Path $dEx.TargetPath))) { $dNeed = $true } } catch { $dNeed = $true }`,
|
|
460
515
|
// ...and a link that is otherwise healthy still needs repairing when its identity is
|
|
461
516
|
// missing or names a different ID/icon. `Read` returns "id|icon", so ONE comparison
|
|
462
517
|
// covers both properties — and it is the same call the verification below prints.
|
|
463
518
|
` if ($dSet) { try { if (([SummriseLnk]::Read($dLnk)) -ne ($dId + '|' + $dRes)) { $dNeed = $true } } catch { $dNeed = $true } }`,
|
|
464
519
|
`}`,
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
//
|
|
468
|
-
|
|
469
|
-
`
|
|
520
|
+
// The Start Menu link is created when ABSENT and rewritten when its identity is wrong —
|
|
521
|
+
// it is never deleted, and a link that already matches is left alone so a second `setup`
|
|
522
|
+
// does not churn the file.
|
|
523
|
+
`$dSmNeed = $false`,
|
|
524
|
+
`if ($dShell) {`,
|
|
525
|
+
` if (-not (Test-Path $dSm)) { $dSmNeed = $true }`,
|
|
526
|
+
` elseif (-not $dSet) { $dSmNeed = $true }`,
|
|
527
|
+
` else { try { if (([SummriseLnk]::Read($dSm)) -ne ($dId + '|' + $dRes)) { $dSmNeed = $true } } catch { $dSmNeed = $true } }`,
|
|
528
|
+
`}`,
|
|
529
|
+
`if ($dNeed -and $dShell) {`,
|
|
530
|
+
...lnk("$dLnk", "1"),
|
|
531
|
+
`}`,
|
|
532
|
+
`if ($dSmNeed -and $dShell) {`,
|
|
533
|
+
...lnk("$dSm", "2"),
|
|
470
534
|
`}`,
|
|
471
535
|
`foreach ($dRx in @('summrise-desktop.exe','summrise-tray.exe')) { $dRp = '${deskDirQ}\\' + $dRx; if (Test-Path $dRp) { try { Remove-Item -Force -ErrorAction Stop $dRp; ('desk: removed retired ' + $dRx) | ${sink} } catch { ('desk: retired ' + $dRx + ' locked, kept') | ${sink} } } }`,
|
|
472
536
|
];
|
|
473
537
|
}
|
|
474
|
-
//
|
|
475
|
-
//
|
|
476
|
-
//
|
|
477
|
-
//
|
|
478
|
-
//
|
|
479
|
-
|
|
538
|
+
// ── THE DESKTOP SHELL'S RECORD: WHAT THE LAUNCHERS CAN SEE, AND WHAT THEY CANNOT ──
|
|
539
|
+
//
|
|
540
|
+
// MEASURED on desktop-14rjcr8 (2026-09-28). The desktop shell was KILLED every ten minutes
|
|
541
|
+
// by `<ExecutionTimeLimit>PT10M</ExecutionTimeLimit>` on the SummriseDesktop task: the
|
|
542
|
+
// launcher ends on `& electron.exe .` — the PowerShell CALL OPERATOR, which WAITS — so the
|
|
543
|
+
// shell lives inside the task's process tree and the scheduler killed the whole tree when
|
|
544
|
+
// the limit expired. The guarded 5-minute pulse then revived it, correctly, and ten minutes
|
|
545
|
+
// later it died again. `logs\startup.log` held 4,015 lines and answered ZERO for `desktop`,
|
|
546
|
+
// ZERO for `electron` and ZERO for `ExecutionTimeLimit`. Nothing in the product recorded
|
|
547
|
+
// it; the operator said "还一直在重启呢" four times, and the answer had to be recovered from
|
|
548
|
+
// the Task Scheduler's own operational log, where Summrise is one line among a stream of
|
|
549
|
+
// Edge update tasks.
|
|
550
|
+
//
|
|
551
|
+
// THE SHAPE ALREADY EXISTED ONE PROCESS OVER. The agent has exactly the right sentence
|
|
552
|
+
// about itself in its own run journal — `runstate.rs`: "previous run DID NOT EXIT CLEANLY —
|
|
553
|
+
// REPLACED by a restart" — and the shell, which is the process that IS killed from outside,
|
|
554
|
+
// had no counterpart. These are that counterpart.
|
|
555
|
+
//
|
|
556
|
+
// WHERE THE LINES GO, AND WHY THERE. `logs\startup.log`, under the SAME registry-first DataDir
|
|
557
|
+
// the agent's own `paths.rs` resolves (`DATA_DIR` above — never a hardcoded ProgramData, and
|
|
558
|
+
// the value is baked in at generation time by the process that DID the lookup). `startup.log`
|
|
559
|
+
// is the file the operator actually grepped. A record in a second file nobody knows to open
|
|
560
|
+
// would be tonight's failure one level down.
|
|
561
|
+
//
|
|
562
|
+
// EVERY LINE CONTAINS THE WHOLE WORD `desktop` — the search that failed was
|
|
563
|
+
// `findstr /i desktop ...\logs\startup.log` — and names `electron` wherever it is the subject.
|
|
564
|
+
//
|
|
565
|
+
// AND THE ONE CASE THAT CANNOT BE RECORDED IS WRITTEN DOWN WHERE IT HAPPENS, in
|
|
566
|
+
// `startDesktopPs` below: a Task Scheduler kill takes the logging process with it, so it
|
|
567
|
+
// leaves NO exit code, ever. The next pulse reports that ABSENCE out loud.
|
|
568
|
+
/** The desktop shell's launcher — and the only place in the product that can see it EXIT.
|
|
569
|
+
*
|
|
570
|
+
* `exit code N` is what tells a crash from a kill, and GETTING IT COST A MEASUREMENT. The
|
|
571
|
+
* obvious form was `& "$dir\node_modules\electron\dist\electron.exe" .` then `$LASTEXITCODE`,
|
|
572
|
+
* and it does not work: **electron.exe is a GUI-subsystem binary and Windows PowerShell 5.1
|
|
573
|
+
* does not set `$LASTEXITCODE` for one.** Measured on desktop-14rjcr8, 2026-09-28:
|
|
574
|
+
*
|
|
575
|
+
* & cmd /c 'exit 7' -> $LASTEXITCODE = 7
|
|
576
|
+
* & electron.exe --version -> returned in 0.05s, $LASTEXITCODE STILL 7 (unchanged)
|
|
577
|
+
* [Diagnostics.Process] variant -> waited, .ExitCode = 0
|
|
578
|
+
*
|
|
579
|
+
* The first version of this file used `&`, and the line it wrote on a real restart read
|
|
580
|
+
* `desktop: electron.exe exited (start=1790589628 exit code lived=1s)` — an empty field
|
|
581
|
+
* where a number belongs. That is the same defect this whole record exists to remove (a
|
|
582
|
+
* surface reporting a step in the grammar of an outcome), so the launch goes through
|
|
583
|
+
* `System.Diagnostics.Process` with `UseShellExecute = $false`: the SAME CreateProcess the
|
|
584
|
+
* call operator uses, same inherited handles, same working directory — plus `WaitForExit()`
|
|
585
|
+
* and a real `.ExitCode`.
|
|
586
|
+
*
|
|
587
|
+
* `WaitForExit()` IS LOAD-BEARING BEYOND THE EXIT CODE: it is why this PowerShell stays
|
|
588
|
+
* alive as the shell's parent, which is why the shell lives inside the task's process tree.
|
|
589
|
+
*
|
|
590
|
+
* WHAT IT CANNOT SEE, AND IT IS THE CASE THAT HAPPENED. The task's action is
|
|
591
|
+
* desktop-pulse.vbs -> ensure-desktop.ps1 -> THIS FILE, and Task Scheduler's
|
|
592
|
+
* `<ExecutionTimeLimit>` terminates that whole tree — electron, this PowerShell and the
|
|
593
|
+
* wscript above it. So a scheduler kill records NOTHING here, and neither do `taskkill /T`
|
|
594
|
+
* and a machine going down: the process that would write the line dies with the shell. On
|
|
595
|
+
* 2026-09-28 PT10M killed the shell every ten minutes and this file, as it stood, said
|
|
596
|
+
* nothing at all. The signature is therefore the ABSENCE of an `exited (start=N …)` line for
|
|
597
|
+
* a start the watchdog watched disappear, and `ensureDesktopPs` says those words on the next
|
|
598
|
+
* pulse rather than leaving the gap to be interpreted.
|
|
599
|
+
*
|
|
600
|
+
* Launches the Electron shell from components\summrise-desktop-electron\ with the working
|
|
601
|
+
* directory set there (electron . resolves src/main.js via package.json main). Intentionally
|
|
602
|
+
* small + ASCII-only (system-locale PS). unit-tested. */
|
|
603
|
+
function startDesktopPs(deskDirQ, logsQ) {
|
|
480
604
|
return [
|
|
605
|
+
"# written by `summrise setup` / `summrise desktop` / `summrise update` -- start the",
|
|
606
|
+
"# Electron shell, and leave a record of the launch and of the exit. The comments stay",
|
|
607
|
+
"# IN the file on purpose: this is what an operator reads when the window is gone.",
|
|
608
|
+
"#",
|
|
609
|
+
"# WHAT THIS CANNOT SEE. The launch below WAITS, so it returns with the shell's exit code",
|
|
610
|
+
"# when the shell exits OR crashes. It does NOT return when the whole process tree is",
|
|
611
|
+
"# killed from outside -- a Task Scheduler execution limit, a `taskkill /T`, a machine",
|
|
612
|
+
"# going down -- because this PowerShell dies first, and then NO exit code is written at",
|
|
613
|
+
"# all. ensure-desktop.ps1 reports that absence on the next pulse; a log that recorded",
|
|
614
|
+
"# only exits would silently omit the one death that actually happens here.",
|
|
615
|
+
"#",
|
|
616
|
+
"# WHY NOT `& electron.exe .` AND $LASTEXITCODE (measured on desktop-14rjcr8, 2026-09-28):",
|
|
617
|
+
"# electron.exe is a GUI-subsystem binary and Windows PowerShell 5.1 does not set",
|
|
618
|
+
"# $LASTEXITCODE for one. `& cmd /c 'exit 7'` set it to 7; the next `& electron.exe",
|
|
619
|
+
"# --version` left it at 7, unchanged. The first version of this file used `&` and wrote",
|
|
620
|
+
"# `exit code ` with the number missing -- a field that looks like a value and is not one.",
|
|
621
|
+
"# System.Diagnostics.Process is the same CreateProcess, and its ExitCode is real.",
|
|
481
622
|
`$dir = '${deskDirQ}'`,
|
|
482
|
-
|
|
483
|
-
|
|
623
|
+
`$log = '${logsQ}\\startup.log'`,
|
|
624
|
+
"New-Item -ItemType Directory -Force -Path (Split-Path $log) | Out-Null",
|
|
625
|
+
"$E = [DateTimeOffset]::UtcNow.ToUnixTimeSeconds()",
|
|
626
|
+
"Add-Content -Path $log -Value ('[' + (Get-Date -Format o) + '] desktop: electron.exe launching from ' + $dir + ' (start=' + $E + ')') -ErrorAction SilentlyContinue",
|
|
627
|
+
"Set-Location $dir",
|
|
628
|
+
"$code = $null",
|
|
629
|
+
"try {",
|
|
630
|
+
" $si = New-Object System.Diagnostics.ProcessStartInfo",
|
|
631
|
+
" $si.FileName = \"$dir\\node_modules\\electron\\dist\\electron.exe\"",
|
|
632
|
+
" $si.Arguments = '.'",
|
|
633
|
+
" $si.WorkingDirectory = $dir",
|
|
634
|
+
" $si.UseShellExecute = $false",
|
|
635
|
+
" $p = [System.Diagnostics.Process]::Start($si)",
|
|
636
|
+
" $p.WaitForExit()",
|
|
637
|
+
" $code = $p.ExitCode",
|
|
638
|
+
"} catch { $code = $null }",
|
|
639
|
+
"$L = [int]([DateTimeOffset]::UtcNow.ToUnixTimeSeconds() - $E)",
|
|
640
|
+
"if ($null -ne $code) {",
|
|
641
|
+
" Add-Content -Path $log -Value ('[' + (Get-Date -Format o) + '] desktop: electron.exe exited (start=' + $E + ' exit code ' + $code + ' lived=' + $L + 's)') -ErrorAction SilentlyContinue",
|
|
642
|
+
"} else {",
|
|
643
|
+
" Add-Content -Path $log -Value ('[' + (Get-Date -Format o) + '] desktop: electron.exe DID NOT RUN (start=' + $E + ') -- the launcher could not start it, so there is no exit code') -ErrorAction SilentlyContinue",
|
|
644
|
+
"}",
|
|
645
|
+
];
|
|
646
|
+
}
|
|
647
|
+
/** The desktop shell's WATCHDOG: start the shell when none is running, and RECORD WHICH OF
|
|
648
|
+
* THE TWO THINGS HAPPENED — in the words a person would search for.
|
|
649
|
+
*
|
|
650
|
+
* Run every 5 minutes by `desktop-pulse.vbs` under the SummriseDesktop task, and once at
|
|
651
|
+
* logon. `desktop-pulse.vbs` itself takes no decision and carries no record.
|
|
652
|
+
*
|
|
653
|
+
* WRITTEN FROM ONE BUILDER, BY ALL THREE PATHS THAT WRITE IT — `summrise setup`,
|
|
654
|
+
* `summrise desktop` and the update swap — because a second copy of this text is a copy
|
|
655
|
+
* that drifts, and the drift would silently revert the record on whichever path was not
|
|
656
|
+
* edited (the shape `installer_integrity.rs` already holds the task definition against).
|
|
657
|
+
*
|
|
658
|
+
* THE HEALTHY CASE IS LOGGED, ONCE AN HOUR, AND BOTH HALVES OF THAT ARE DELIBERATE.
|
|
659
|
+
*
|
|
660
|
+
* A line per 5-minute pulse is 288 lines a day of "still fine" in `startup.log`, which is
|
|
661
|
+
* the AGENT's boot record and rotates at 1 MB (`main.rs::log_line`). That heartbeat would
|
|
662
|
+
* evict the history of a real incident — the opposite of the point — and an operator
|
|
663
|
+
* grepping `desktop` after a restart would have to wade through it to find the restart.
|
|
664
|
+
*
|
|
665
|
+
* Writing NOTHING in the healthy case is worse in the other direction: a watchdog that has
|
|
666
|
+
* stopped and a watchdog that is fine would look identical, because both are SILENCE — this
|
|
667
|
+
* repository's own recorded defect class, where "if nothing was checked, say that" is the
|
|
668
|
+
* honest form. So: ONE line an hour (~2 KB a day), deduplicated against the LOG ITSELF
|
|
669
|
+
* rather than a state file (the record is the state, so there is nothing to get out of
|
|
670
|
+
* sync), and a gap longer than about two hours is the signal that the pulse itself stopped.
|
|
671
|
+
*
|
|
672
|
+
* THE DECISION IS STATED AS THE OBSERVATION, NOT AS THE STEP: "no electron shell is
|
|
673
|
+
* running" is what the pulse SAW; the launch it then performs is the consequence. When the
|
|
674
|
+
* shell that disappeared left no `exited (start=N …)` line, this says so in words — that is
|
|
675
|
+
* the scheduler-kill signature, and it is exactly the case `startDesktopPs` cannot record.
|
|
676
|
+
*
|
|
677
|
+
* ASCII-only (system-locale PS). unit-tested. */
|
|
678
|
+
function ensureDesktopPs(scriptsQ, logsQ) {
|
|
679
|
+
return [
|
|
680
|
+
"# written by `summrise setup` / `summrise desktop` / `summrise update` -- the desktop",
|
|
681
|
+
"# shell's watchdog, run every 5 minutes by desktop-pulse.vbs under SummriseDesktop.",
|
|
682
|
+
"# Start the shell if none is running, and RECORD WHICH OF THE TWO THINGS HAPPENED:",
|
|
683
|
+
"# silence is not a status. The builder in src/summrise.ts carries the full reasoning,",
|
|
684
|
+
"# including why the healthy case is one line an hour and not one per pulse.",
|
|
685
|
+
`$log = '${logsQ}\\startup.log'`,
|
|
686
|
+
"New-Item -ItemType Directory -Force -Path (Split-Path $log) | Out-Null",
|
|
687
|
+
"$now = [DateTimeOffset]::UtcNow.ToUnixTimeSeconds()",
|
|
688
|
+
"$tail = @(Get-Content -Path $log -Tail 400 -ErrorAction SilentlyContinue)",
|
|
689
|
+
"$p = @(Get-Process electron -ErrorAction SilentlyContinue)",
|
|
690
|
+
"if ($p.Count -gt 0) {",
|
|
691
|
+
" $h = 'hour=' + (Get-Date -Format 'yyyy-MM-ddTHH')",
|
|
692
|
+
" if (-not ($tail -match ('desktop: electron shell alive .*' + $h))) {",
|
|
693
|
+
" $up = ($p | Sort-Object StartTime | Select-Object -First 1).StartTime",
|
|
694
|
+
" Add-Content -Path $log -Value ('[' + (Get-Date -Format o) + '] desktop: electron shell alive -- ' + $p.Count + ' process(es), oldest since ' + $up + ' ' + $h) -ErrorAction SilentlyContinue",
|
|
695
|
+
" }",
|
|
696
|
+
" exit",
|
|
697
|
+
"}",
|
|
698
|
+
"$was = 'no earlier launch is recorded in the log tail'",
|
|
699
|
+
// THE PATTERN MUST MATCH THE LINE `startDesktopPs` ACTUALLY WRITES, and the first version of
|
|
700
|
+
// it did NOT: it read `launching \(start=` while the launcher writes `launching from <dir>
|
|
701
|
+
// (start=`. Every pulse therefore fell through to "no earlier launch is recorded in the log
|
|
702
|
+
// tail" and the previous shell's lifetime was never reported. MEASURED on desktop-14rjcr8 —
|
|
703
|
+
// the pulse said exactly that at 18:06:53 while the launching line it was looking for sat
|
|
704
|
+
// thirty lines above it in the same file. The ` .*` is the fix, and test/cli.test.mjs now
|
|
705
|
+
// runs THIS pattern against a REAL captured line, which is the check that would have caught it.
|
|
706
|
+
"$prev = $tail | Select-String -Pattern 'desktop: electron.exe launching .*\\(start=(\\d+)\\)' | Select-Object -Last 1",
|
|
707
|
+
"if ($prev -and ($prev.Line -match '\\(start=(\\d+)\\)')) {",
|
|
708
|
+
" $E = [int]$Matches[1]",
|
|
709
|
+
" if ($tail -match ('desktop: electron\\.exe exited \\(start=' + $E + ' ')) { $was = 'the previous shell exited and its code is in the exited (start=' + $E + ') line' }",
|
|
710
|
+
" else { $was = 'the previous shell lived ' + ([int]$now - $E) + 's and left NO exit line -- it was KILLED, not exited: a Task Scheduler stop, a taskkill /T or a reboot leaves no exit code (start=' + $E + ')' }",
|
|
711
|
+
"}",
|
|
712
|
+
"Add-Content -Path $log -Value ('[' + (Get-Date -Format o) + '] desktop: no electron shell is running -- the watchdog is starting it; previous: ' + $was) -ErrorAction SilentlyContinue",
|
|
713
|
+
`& powershell -NoProfile -ExecutionPolicy Bypass -File "${scriptsQ}\\start-desktop.ps1"`,
|
|
484
714
|
];
|
|
485
715
|
}
|
|
486
716
|
/** The desktop shell's watchdog pair + its task, as ONE PowerShell script.
|
|
@@ -492,20 +722,52 @@ function startDesktopPs(deskDirQ) {
|
|
|
492
722
|
* never ran setup can still get a working task by asking for the window. Idempotent:
|
|
493
723
|
* ensure-desktop.ps1 exits when electron is already alive, so the 5-minute trigger never
|
|
494
724
|
* steals focus. unit-tested. */
|
|
495
|
-
function desktopTaskPs(installQ) {
|
|
725
|
+
function desktopTaskPs(installQ, logsQ) {
|
|
496
726
|
return [
|
|
497
727
|
"# written by `summrise setup` / `summrise desktop` -- the desktop shell's task + watchdog.",
|
|
498
728
|
"$ErrorActionPreference = 'Stop'",
|
|
499
729
|
`$q = '${installQ}'`,
|
|
500
730
|
`$en = Join-Path $q 'scripts\\ensure-desktop.ps1'`,
|
|
501
731
|
`$vb = Join-Path $q 'scripts\\desktop-pulse.vbs'`,
|
|
502
|
-
|
|
732
|
+
// THE WATCHDOG'S BODY COMES FROM ITS OWN BUILDER, not from a second hand-typed copy of
|
|
733
|
+
// the same one-liner. Three paths write this file — `summrise setup`, `summrise desktop`
|
|
734
|
+
// and the update swap — and a copy that drifts is a copy that silently reverts the
|
|
735
|
+
// shell's record on whichever path was not edited. A single-quoted here-string
|
|
736
|
+
// (`@'` … `'@`, each alone on its line) carries it byte for byte: nothing in the body is
|
|
737
|
+
// expanded, and nothing in the body can end it early.
|
|
738
|
+
`$enBody = @'`,
|
|
739
|
+
...ensureDesktopPs(`${installQ}\\scripts`, logsQ),
|
|
740
|
+
`'@`,
|
|
741
|
+
`Set-Content -Path $en -Value $enBody -Force`,
|
|
503
742
|
`Set-Content -Path $vb -Value ('CreateObject("WScript.Shell").Run "powershell -NoProfile -ExecutionPolicy Bypass -File " & Chr(34) & "' + $en + '" & Chr(34), 0, False') -Force`,
|
|
504
743
|
`$da = New-ScheduledTaskAction -Execute 'wscript.exe' -Argument ('"' + $vb + '"') -WorkingDirectory $q`,
|
|
505
744
|
`$dt1 = New-ScheduledTaskTrigger -AtLogOn`,
|
|
506
745
|
`$dw1 = New-ScheduledTaskTrigger -Once -At (Get-Date).AddMinutes(3) -RepetitionInterval (New-TimeSpan -Minutes 5)`,
|
|
507
746
|
`$pr = New-ScheduledTaskPrincipal -UserId ('{0}\\{1}' -f $env:USERDOMAIN, $env:USERNAME) -LogonType Interactive -RunLevel Highest`,
|
|
508
|
-
|
|
747
|
+
// NO EXECUTION TIME LIMIT -- the same value, in the same spelling, the agent's own task has
|
|
748
|
+
// carried since round 118 for the same reason: `- ExecutionTimeLimit 0 never kill the
|
|
749
|
+
// running task`. It was `-Minutes 10` here, and that WAS the defect.
|
|
750
|
+
//
|
|
751
|
+
// WHY A LIMIT KILLS A SHELL MEANT TO LIVE INDEFINITELY: this task's action is
|
|
752
|
+
// desktop-pulse.vbs -> ensure-desktop.ps1 -> start-desktop.ps1, and that last line is
|
|
753
|
+
// `& electron.exe .` -- the PowerShell CALL OPERATOR, which WAITS. The PowerShell the task
|
|
754
|
+
// launched therefore stays alive for as long as the shell does, as the shell's PARENT, and
|
|
755
|
+
// Task Scheduler enforces ExecutionTimeLimit on that process tree. Ten minutes after every
|
|
756
|
+
// start the scheduler killed the running shell; five minutes later the guarded pulse
|
|
757
|
+
// correctly brought a fresh one back; ten minutes after that it died again. The operator
|
|
758
|
+
// reported that as "反复重启", four times.
|
|
759
|
+
//
|
|
760
|
+
// NOTHING RELIED ON THE KILL, which is why removing it is the fix rather than a trade. This
|
|
761
|
+
// repository's recovery for a shell that is GONE is the guarded 5-minute pulse, and
|
|
762
|
+
// summrise-desktop-electron/src/main.ts says so from the other side, naming a logon-only task
|
|
763
|
+
// as one that "cannot recover a wedged shell". A shell that is RUNNING but wedged was never
|
|
764
|
+
// this limit's business either: the pulse's guard is a process-EXISTENCE check
|
|
765
|
+
// (`if (Get-Process electron) { exit }`), so it cannot see a wedged shell whether the limit is
|
|
766
|
+
// ten minutes or absent. `summrise update` is what heals a wedged shell, and its log says so.
|
|
767
|
+
//
|
|
768
|
+
// Measured on desktop-14rjcr8: under PT10M the four electron PIDs were replaced about every
|
|
769
|
+
// ten minutes; under PT0S the same four PIDs were still there 11 minutes later.
|
|
770
|
+
`$st = New-ScheduledTaskSettingsSet -ExecutionTimeLimit (New-TimeSpan -Seconds 0) -AllowStartIfOnBatteries -DontStopIfGoingOnBatteries -StartWhenAvailable -MultipleInstances IgnoreNew`,
|
|
509
771
|
`Register-ScheduledTask SummriseDesktop -Action $da -Trigger @($dt1,$dw1) -Principal $pr -Settings $st -Force | Out-Null`,
|
|
510
772
|
`Start-ScheduledTask -TaskName SummriseDesktop`,
|
|
511
773
|
];
|
|
@@ -2206,7 +2468,7 @@ const commands = {
|
|
|
2206
2468
|
// repetition is what reborns a dead shell.
|
|
2207
2469
|
try {
|
|
2208
2470
|
const reg = path.join(SCRIPTS_DIR, "register-desktop-task.ps1");
|
|
2209
|
-
fs.writeFileSync(reg, desktopTaskPs(DIR).join("\r\n") + "\r\n");
|
|
2471
|
+
fs.writeFileSync(reg, desktopTaskPs((0, exports.psq)(DIR), (0, exports.psq)(LOGS_DIR)).join("\r\n") + "\r\n");
|
|
2210
2472
|
// THE STATUS WAS DISCARDED AND THE SUCCESS LINE WAS UNCONDITIONAL, which is the defect the AGENT
|
|
2211
2473
|
// task's check ten lines below names in its own comment: "audit #7: used to claim success
|
|
2212
2474
|
// regardless". Worse, the `catch` below could not fire for this — `sh()` RETURNS, it does not
|
|
@@ -2237,7 +2499,7 @@ const commands = {
|
|
|
2237
2499
|
// SummriseDesktop onlogon task + desktop Summrise.lnk both call it). Was never
|
|
2238
2500
|
// written before — a real gap that left the shell unlaunchable.
|
|
2239
2501
|
fs.mkdirSync(SCRIPTS_DIR, { recursive: true });
|
|
2240
|
-
fs.writeFileSync(path.join(SCRIPTS_DIR, "start-desktop.ps1"), startDesktopPs((0, exports.psq)(DESK_DIR)).join("\r\n"), "utf8");
|
|
2502
|
+
fs.writeFileSync(path.join(SCRIPTS_DIR, "start-desktop.ps1"), startDesktopPs((0, exports.psq)(DESK_DIR), (0, exports.psq)(LOGS_DIR)).join("\r\n"), "utf8");
|
|
2241
2503
|
console.log("setup: scripts\\start-desktop.ps1 written");
|
|
2242
2504
|
// Register boot-start task (SYSTEM) and kick it once; the agent's own
|
|
2243
2505
|
// first-run flow registers the device with the console using the key.
|
|
@@ -2474,7 +2736,7 @@ const commands = {
|
|
|
2474
2736
|
if (!fs.existsSync(reg)) {
|
|
2475
2737
|
// A machine that never ran setup still gets a working task: the same script setup
|
|
2476
2738
|
// writes, from the same builder, so the two cannot drift.
|
|
2477
|
-
fs.writeFileSync(reg, desktopTaskPs(DIR).join("\r\n") + "\r\n");
|
|
2739
|
+
fs.writeFileSync(reg, desktopTaskPs((0, exports.psq)(DIR), (0, exports.psq)(LOGS_DIR)).join("\r\n") + "\r\n");
|
|
2478
2740
|
}
|
|
2479
2741
|
// THE START SCRIPT GOES IN A FILE, AND ONLY IT DECIDES WHETHER TO REGISTER. Spawning with
|
|
2480
2742
|
// `shell: true` means cmd.exe, and **cmd splits a command on `&`** — the PowerShell call
|
|
@@ -2924,7 +3186,7 @@ const commands = {
|
|
|
2924
3186
|
// the swap script's migration, and a headless install simply never
|
|
2925
3187
|
// calls the launcher (shortcut repair is Test-Path guarded anyway).
|
|
2926
3188
|
fs.mkdirSync(SCRIPTS_DIR, { recursive: true });
|
|
2927
|
-
fs.writeFileSync(path.join(SCRIPTS_DIR, "start-desktop.ps1"), startDesktopPs((0, exports.psq)(DESK_DIR)).join("\r\n"), "utf8");
|
|
3189
|
+
fs.writeFileSync(path.join(SCRIPTS_DIR, "start-desktop.ps1"), startDesktopPs((0, exports.psq)(DESK_DIR), (0, exports.psq)(LOGS_DIR)).join("\r\n"), "utf8");
|
|
2928
3190
|
// round-298: record the release version on a PROVABLY successful swap
|
|
2929
3191
|
// so agent_update (which reads <install>/.summrise-release as its local
|
|
2930
3192
|
// version) reports up_to_date instead of re-swapping every call.
|
|
@@ -3015,13 +3277,29 @@ const commands = {
|
|
|
3015
3277
|
// wscript wrapper runs it with no console flash.
|
|
3016
3278
|
`$en1 = '${q}\\scripts\\ensure-desktop.ps1'`,
|
|
3017
3279
|
`$vb1 = '${q}\\scripts\\desktop-pulse.vbs'`,
|
|
3018
|
-
|
|
3280
|
+
// THE SAME BUILDER `setup` USES, and this is the path that matters most: an update is
|
|
3281
|
+
// the ONLY mechanism that reaches a machine already installed, so a watchdog written
|
|
3282
|
+
// here from a second, hand-typed copy would silently overwrite the instrumented one on
|
|
3283
|
+
// every fleet device while `setup` looked correct. `$en1` is assigned above and the
|
|
3284
|
+
// here-string needs no interpolation, so the body travels verbatim.
|
|
3285
|
+
`$enBody = @'`,
|
|
3286
|
+
...ensureDesktopPs(`${q}\\scripts`, qd),
|
|
3287
|
+
`'@`,
|
|
3288
|
+
`Set-Content -Path $en1 -Value $enBody -Force`,
|
|
3019
3289
|
`Set-Content -Path $vb1 -Value 'CreateObject("WScript.Shell").Run "powershell -NoProfile -ExecutionPolicy Bypass -File " & Chr(34) & "${q}\\scripts\\ensure-desktop.ps1" & Chr(34), 0, False' -Force`,
|
|
3020
3290
|
`if ($null -ne (Get-ScheduledTask -TaskName 'SummriseDesktop' -ErrorAction SilentlyContinue)) {`,
|
|
3021
3291
|
` $da = New-ScheduledTaskAction -Execute 'wscript.exe' -Argument ('"' + $vb1 + '"') -WorkingDirectory '${q}'`,
|
|
3022
3292
|
` $dt1 = New-ScheduledTaskTrigger -AtLogOn`,
|
|
3023
3293
|
` $dw1 = New-ScheduledTaskTrigger -Once -At (Get-Date).AddMinutes(3) -RepetitionInterval (New-TimeSpan -Minutes 5)`,
|
|
3024
|
-
|
|
3294
|
+
// AND THE SETTINGS, WHICH IS THE HALF THAT REACHES A MACHINE ALREADY INSTALLED.
|
|
3295
|
+
// This call used to pass only -Action and -Trigger, and `Set-ScheduledTask` PRESERVES the
|
|
3296
|
+
// components it is not given — so an execution limit already on the task survived every
|
|
3297
|
+
// update. That is the wrong shape for a heal: `summrise update` is the only mechanism that
|
|
3298
|
+
// reaches the fleet, and a device carrying the old 10-minute limit would have kept it
|
|
3299
|
+
// forever while its log said "hardened". The value is the same one `setup` writes, so the
|
|
3300
|
+
// two cannot diverge — and installer_integrity.rs holds both ends to it.
|
|
3301
|
+
` $dst = New-ScheduledTaskSettingsSet -ExecutionTimeLimit (New-TimeSpan -Seconds 0) -AllowStartIfOnBatteries -DontStopIfGoingOnBatteries -StartWhenAvailable -MultipleInstances IgnoreNew`,
|
|
3302
|
+
` Set-ScheduledTask -TaskName 'SummriseDesktop' -Action $da -Trigger @($dt1, $dw1) -Settings $dst | Out-Null`,
|
|
3025
3303
|
` "[$(Get-Date -Format o)] desk: SummriseDesktop hardened (guarded 5-min pulse)" | ${log}`,
|
|
3026
3304
|
`}`,
|
|
3027
3305
|
`if ((Test-Path $deskDir) -and (Test-Path $deskStart)) {`,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "summrise-agent",
|
|
3
|
-
"version": "1.2.
|
|
3
|
+
"version": "1.2.493",
|
|
4
4
|
"description": "Summrise Agent — Windows device MCP server (terminal/SSH/serial) + panel. Self-hosted npm distribution.",
|
|
5
5
|
"//build": "唯一构建路径:在本目录执行 npm run build。它是自包含的:经 npm exec 按需拉取 typescript@5,无需预装任何 node_modules(干净检出可跑;旧脚本引用 ../summrise-desktop-electron/node_modules/.bin/tsc,干净检出必 ENOENT)。必须用 npm exec --package … -- tsc 的形式:npx 会吃掉传给 tsc 的 -p/--outDir 等参数(round-435:npx 写法从未真正跑通过)。先编 electron TS(--noCheck 纯 emit,与 release.yml 一致)同步两处 src/*.js,再编 src/summrise.ts 到 bin/summrise.js 并校验 round-298 标记(弱 oracle,只证某次构建发生,新鲜性由 publish-release.sh 的 tsc 重编 + cmp 门保证)。",
|
|
6
6
|
"bin": {
|
package/summrise-agent.exe
CHANGED
|
Binary file
|
|
@@ -909,10 +909,17 @@ if (gotTheLock) {
|
|
|
909
909
|
// there) as System.AppUserModel.ID — the same string — plus RelaunchIconResource pointing
|
|
910
910
|
// at the sunrise .ico. KEEP THE TWO IN STEP; the literal below is a copy because this file
|
|
911
911
|
// is emitted to plain JS in two packages and cannot import the CLI's module.
|
|
912
|
+
// THE VALUE IS NOT THE ONE THIS FILE USED BEFORE ("…summrise.agent"), and that is the
|
|
913
|
+
// measured half of the fix rather than a rename. On desktop-14rjcr8, with that string set
|
|
914
|
+
// on the window AND read back off the shortcut, the taskbar still drew the Electron logo:
|
|
915
|
+
// releases up to 1.2.489 had already resolved that AUMID — set with no shortcut anywhere —
|
|
916
|
+
// against the backing executable and kept the answer, and `shell:AppsFolder` still lists it
|
|
917
|
+
// as an app named "Electron". The same machine drew the SUNRISE under a string it had never
|
|
918
|
+
// seen, with every other variable held. DESKTOP_AUMID carries the two-row table.
|
|
912
919
|
// (The same shortcut association is also the documented precondition for the hide-to-tray
|
|
913
920
|
// toast below: Electron requires a Start Menu shortcut carrying the ID and a
|
|
914
921
|
// ToastActivatorCLSID, and this one carries neither — unchanged by this call.)
|
|
915
|
-
const AUMID = "online.saisi.summrise.
|
|
922
|
+
const AUMID = "online.saisi.summrise.desktop";
|
|
916
923
|
try {
|
|
917
924
|
if (process.platform === "win32")
|
|
918
925
|
electron_1.app.setAppUserModelId(AUMID);
|