summrise-agent 1.2.490 → 1.2.492
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 +243 -7
- package/package.json +1 -1
- package/summrise-agent.exe +0 -0
- package/summrise-desktop-electron/src/main.js +100 -64
package/bin/summrise.js
CHANGED
|
@@ -34,8 +34,9 @@ var __importStar = (this && this.__importStar) || (function () {
|
|
|
34
34
|
};
|
|
35
35
|
})();
|
|
36
36
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
37
|
-
exports.BOOT_TASKS = exports.psq = void 0;
|
|
37
|
+
exports.BOOT_TASKS = exports.DESKTOP_AUMID = exports.psq = void 0;
|
|
38
38
|
exports.psArgv = psArgv;
|
|
39
|
+
exports.lnkIdentity = lnkIdentity;
|
|
39
40
|
exports.deskShortcutRepairPs = deskShortcutRepairPs;
|
|
40
41
|
exports.startDesktopPs = startDesktopPs;
|
|
41
42
|
exports.desktopTaskPs = desktopTaskPs;
|
|
@@ -298,13 +299,169 @@ function psFile(script) {
|
|
|
298
299
|
// exported: unit-tested in test/cli.test.mjs (SYSTEM-context PS quoting = injection surface)
|
|
299
300
|
const psq = (x) => String(x).replace(/'/g, "''");
|
|
300
301
|
exports.psq = psq;
|
|
302
|
+
/** The taskbar identity: ONE string, and it lives on BOTH sides of an association.
|
|
303
|
+
*
|
|
304
|
+
* It is the AppUserModelID the desktop shell sets on its process
|
|
305
|
+
* (`agent/summrise-desktop-electron/src/main.ts`) and the value written into
|
|
306
|
+
* `System.AppUserModel.ID` on the shortcut below. Microsoft's lookup for the taskbar
|
|
307
|
+
* group's icon takes the shortcut arm only when the window carries an explicit ID, and
|
|
308
|
+
* the window's arm only names a shortcut carrying the SAME ID — so the two halves are one
|
|
309
|
+
* association and this is the string that ties them. If they ever disagree, the icon
|
|
310
|
+
* silently reverts to the backing executable (stock `electron.exe`, since the shell runs
|
|
311
|
+
* unpackaged) with nothing on the device to read that says why.
|
|
312
|
+
* PowerShell/Windows AppUserModelIDs cannot contain spaces, and this one does not.
|
|
313
|
+
*
|
|
314
|
+
* WHY IT IS NOT `online.saisi.summrise.agent`, WHICH IS WHAT IT WAS CALLED BEFORE —
|
|
315
|
+
* MEASURED on desktop-14rjcr8 (release 1.2.490), ONE VARIABLE AT A TIME:
|
|
316
|
+
*
|
|
317
|
+
* | window's AppUserModelID | shortcut carries it | taskbar draws |
|
|
318
|
+
* | window's AppUserModelID | shortcuts carrying it | taskbar draws |
|
|
319
|
+
* | online.saisi.summrise.agent | both desktops + Start Menu | Electron logo |
|
|
320
|
+
* | online.saisi.summrise.desktop | both desktops + Start Menu | THE SUNRISE |
|
|
321
|
+
* | online.saisi.summrise.desktop | both desktops, Start Menu GONE | Electron logo |
|
|
322
|
+
*
|
|
323
|
+
* That third row is the one that decides where the identity has to live, and it was taken
|
|
324
|
+
* WITHOUT restarting anything: the same running shell, the same AppUserModelID already set,
|
|
325
|
+
* and the icon fell back to the Electron logo the moment the Start Menu shortcut was
|
|
326
|
+
* deleted. So the shortcut this lookup resolves is the **Start Menu** one — a desktop
|
|
327
|
+
* shortcut is not enough, and writing the identity onto `Summrise.lnk` in
|
|
328
|
+
* `%PUBLIC%\Desktop` alone changes nothing. Microsoft's appids.md does say to apply the ID
|
|
329
|
+
* to "all of the shortcuts" when an application has several launch locations, and it lists
|
|
330
|
+
* the Start menu FIRST; the measurement is what says which of them the taskbar actually
|
|
331
|
+
* reads.
|
|
332
|
+
*
|
|
333
|
+
* The other variables were held: the same `IPropertyStore` call read back on every
|
|
334
|
+
* shortcut, the same `icon.ico` (downloaded and RENDERED — it is the orange sunrise), the
|
|
335
|
+
* same shell restarted under the `SummriseDesktop` task, and in each restarted run the
|
|
336
|
+
* running process appended `AUMID-SET-OK` to a log from inside `setAppUserModelId`, so "the
|
|
337
|
+
* ID was set" is measured rather than assumed.
|
|
338
|
+
*
|
|
339
|
+
* WHY THE OLD ID WAS DEAD. Releases up to 1.2.489 set `online.saisi.summrise.agent` on the
|
|
340
|
+
* process while no shortcut anywhere carried it, so the shell resolved that ID against the
|
|
341
|
+
* backing executable — stock `electron.exe` — and kept the result. `shell:AppsFolder` still
|
|
342
|
+
* lists it as `name=[Electron] path=[online.saisi.summrise.agent]`, an app entry named after
|
|
343
|
+
* `electron.exe`. An ID already resolved to the wrong image is not repaired by writing the
|
|
344
|
+
* shortcut afterwards; the association has to be under a name the machine has never seen.
|
|
345
|
+
* Keep this string STABLE from here: changing it again costs the pin and the jump list, so
|
|
346
|
+
* this is a one-time migration, not a knob.
|
|
347
|
+
*/
|
|
348
|
+
exports.DESKTOP_AUMID = "online.saisi.summrise.desktop";
|
|
349
|
+
/** The two `IPropertyStore` values `Summrise.lnk` must carry, as DATA.
|
|
350
|
+
*
|
|
351
|
+
* Deliberately separated from the PowerShell that performs the write, and exported, so the
|
|
352
|
+
* DECISION is testable where the COM call is not: a test can pin what the CLI intends to
|
|
353
|
+
* write without a Windows box. `icoPath` is the shell's own sunrise `.ico`; the `,0` is the
|
|
354
|
+
* icon index and is REQUIRED — the property's own documentation: "because .ico files can
|
|
355
|
+
* contain multiple icon resources, a resource ID is required in the string. If the .ico
|
|
356
|
+
* file is a single image, use '0'".
|
|
357
|
+
*
|
|
358
|
+
* `IconLocation` on the shortcut is a DIFFERENT thing and is left alone: it is what
|
|
359
|
+
* Explorer draws for the .lnk FILE. These two are what the taskbar GROUP uses.
|
|
360
|
+
*/
|
|
361
|
+
function lnkIdentity(icoPath) {
|
|
362
|
+
return { id: exports.DESKTOP_AUMID, icon: `${icoPath},0` };
|
|
363
|
+
}
|
|
364
|
+
/** C# interop for the two shortcut properties `WScript.Shell` cannot set.
|
|
365
|
+
*
|
|
366
|
+
* WHY THIS MECHANISM, given this repository prefers Rust for logic: `System.AppUserModel.ID`
|
|
367
|
+
* and `System.AppUserModel.RelaunchIconResource` are not fields of the .lnk header — they
|
|
368
|
+
* live in the shortcut's `IPropertyStore`, and `WScript.Shell` exposes no way to reach one.
|
|
369
|
+
* The route Microsoft documents for a shortcut is `IShellLink` + `QueryInterface` for
|
|
370
|
+
* `IPropertyStore` ("A shortcut (as an IShellLink, CLSID_ShellLink, or a .lnk file) supports
|
|
371
|
+
* properties through IPropertyStore", shell/appids.md) — COM interop, in the launcher's own
|
|
372
|
+
* script generator, which is already PowerShell and already writing this shortcut.
|
|
373
|
+
* `Add-Type` compiles it with the C# compiler that ships IN the box: nothing installed,
|
|
374
|
+
* nothing downloaded, and it runs on a stock Windows box with no Node, no Rust and no SDK.
|
|
375
|
+
* A Rust helper is the repository's usual preference and was rejected HERE on cost, not on
|
|
376
|
+
* taste: it needs a new cross-compiled binary in the npm package, and this package "carries
|
|
377
|
+
* NO boxed components" by design — `setup` fetches and hash-pins them — so ~60 lines of
|
|
378
|
+
* interop would buy a build stage, a release-host component and a new pin.
|
|
379
|
+
*
|
|
380
|
+
* TWO THINGS THE DEVICE TAUGHT, both load-bearing:
|
|
381
|
+
* - `IPersistFile::Load` must be given STGM_READWRITE (2). Loading with 0 opens the link
|
|
382
|
+
* READ-ONLY and the later `IPropertyStore::Commit` fails STG_E_ACCESSDENIED (0x80030005)
|
|
383
|
+
* — measured on desktop-14rjcr8, where the first version of this passed 0 and wrote
|
|
384
|
+
* nothing while reporting no error the caller could see.
|
|
385
|
+
* - the write is verified by READING THE PROPERTIES BACK with `Read`, which is also what
|
|
386
|
+
* decides whether the shortcut needs repairing at all.
|
|
387
|
+
*/
|
|
388
|
+
const LNK_PROPS_CS = `using System;
|
|
389
|
+
using System.Runtime.InteropServices;
|
|
390
|
+
public static class SummriseLnk {
|
|
391
|
+
[StructLayout(LayoutKind.Sequential, Pack = 4)]
|
|
392
|
+
struct PROPERTYKEY { public Guid fmtid; public uint pid; }
|
|
393
|
+
[StructLayout(LayoutKind.Sequential)]
|
|
394
|
+
struct PROPVARIANT { public ushort vt, r1, r2, r3; public IntPtr p, p2; }
|
|
395
|
+
[ComImport, Guid("886D8EEB-8CF2-4446-8D02-CDBA1DBDCF99"), InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
|
|
396
|
+
interface IPropertyStore {
|
|
397
|
+
int GetCount(out uint c);
|
|
398
|
+
int GetAt(uint i, out PROPERTYKEY k);
|
|
399
|
+
int GetValue(ref PROPERTYKEY k, out PROPVARIANT v);
|
|
400
|
+
int SetValue(ref PROPERTYKEY k, ref PROPVARIANT v);
|
|
401
|
+
int Commit();
|
|
402
|
+
}
|
|
403
|
+
[ComImport, Guid("0000010b-0000-0000-C000-000000000046"), InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
|
|
404
|
+
interface IPersistFile {
|
|
405
|
+
void GetClassID(out Guid g);
|
|
406
|
+
[PreserveSig] int IsDirty();
|
|
407
|
+
void Load([MarshalAs(UnmanagedType.LPWStr)] string f, uint m);
|
|
408
|
+
void Save([MarshalAs(UnmanagedType.LPWStr)] string f, [MarshalAs(UnmanagedType.Bool)] bool r);
|
|
409
|
+
void SaveCompleted([MarshalAs(UnmanagedType.LPWStr)] string f);
|
|
410
|
+
void GetCurFile([MarshalAs(UnmanagedType.LPWStr)] out string f);
|
|
411
|
+
}
|
|
412
|
+
static readonly Guid CLSID = new Guid("00021401-0000-0000-C000-000000000046");
|
|
413
|
+
static readonly Guid FMT = new Guid("9F4C2855-9F79-4B39-A8D0-E1D42DE1D5F3");
|
|
414
|
+
const uint RW = 2;
|
|
415
|
+
static PROPERTYKEY K(uint pid) { PROPERTYKEY k; k.fmtid = FMT; k.pid = pid; return k; }
|
|
416
|
+
static object New(string lnk, uint mode) {
|
|
417
|
+
object o = Activator.CreateInstance(Type.GetTypeFromCLSID(CLSID));
|
|
418
|
+
((IPersistFile)o).Load(lnk, mode);
|
|
419
|
+
return o;
|
|
420
|
+
}
|
|
421
|
+
static string Get(string lnk, uint pid, uint mode) {
|
|
422
|
+
IPropertyStore s = (IPropertyStore)New(lnk, mode);
|
|
423
|
+
PROPERTYKEY k = K(pid); PROPVARIANT v;
|
|
424
|
+
if (s.GetValue(ref k, out v) != 0) return "";
|
|
425
|
+
if (v.vt != 31 || v.p == IntPtr.Zero) return "";
|
|
426
|
+
string r = Marshal.PtrToStringUni(v.p);
|
|
427
|
+
Marshal.FreeCoTaskMem(v.p);
|
|
428
|
+
return r;
|
|
429
|
+
}
|
|
430
|
+
static void Put(IPropertyStore s, uint pid, string val) {
|
|
431
|
+
PROPERTYKEY k = K(pid); PROPVARIANT v = new PROPVARIANT();
|
|
432
|
+
v.vt = 31; v.p = Marshal.StringToCoTaskMemUni(val);
|
|
433
|
+
try { int hr = s.SetValue(ref k, ref v); if (hr != 0) Marshal.ThrowExceptionForHR(hr); }
|
|
434
|
+
finally { Marshal.FreeCoTaskMem(v.p); }
|
|
435
|
+
}
|
|
436
|
+
public static string Read(string lnk) { return Get(lnk, 5, 0) + "|" + Get(lnk, 3, 0); }
|
|
437
|
+
public static void Write(string lnk, string id, string icon) {
|
|
438
|
+
object o = New(lnk, RW);
|
|
439
|
+
IPropertyStore s = (IPropertyStore)o;
|
|
440
|
+
Put(s, 5, id); Put(s, 3, icon);
|
|
441
|
+
int hr = s.Commit();
|
|
442
|
+
if (hr != 0) Marshal.ThrowExceptionForHR(hr);
|
|
443
|
+
((IPersistFile)o).Save(lnk, true);
|
|
444
|
+
}
|
|
445
|
+
}`;
|
|
301
446
|
// stage-brand: healed desktop shortcut. A 2026-09-01 Summrise.lnk on devices
|
|
302
447
|
// points at the RETIRED Tauri summrise-desktop.exe (embedded stale icon) —
|
|
303
448
|
// double-clicking it launches the dead app instead of the Electron shell.
|
|
304
449
|
// Repair (only if the link already exists — headless installs must not
|
|
305
450
|
// sprout desktop icons): repoint at start-desktop.ps1 (the SummriseDesktop
|
|
306
|
-
// onlogon path) with IconLocation pinned to the sunrise icon.ico,
|
|
307
|
-
//
|
|
451
|
+
// onlogon path) with IconLocation pinned to the sunrise icon.ico, remove the
|
|
452
|
+
// retired Tauri/tray orphans nothing ships anymore, AND write the taskbar
|
|
453
|
+
// identity (System.AppUserModel.ID + RelaunchIconResource) into the link's
|
|
454
|
+
// property store.
|
|
455
|
+
//
|
|
456
|
+
// WHY THE IDENTITY IS PART OF *THIS* FUNCTION: a shortcut with the right target
|
|
457
|
+
// and the wrong (or absent) identity is exactly the state that draws the
|
|
458
|
+
// Electron logo — the shell sets the same AppUserModelID on its process, and the
|
|
459
|
+
// taskbar's icon lookup reaches the shortcut arm only when a shortcut carrying
|
|
460
|
+
// that ID can be found. MEASURED on desktop-14rjcr8 before this change: both
|
|
461
|
+
// Summrise.lnk files existed and carried NEITHER property, which is why an
|
|
462
|
+
// explicit ID on the window alone changed nothing and why deleting that ID
|
|
463
|
+
// (d4edbb64) changed nothing either.
|
|
464
|
+
//
|
|
308
465
|
// Best-effort + logged, never fatal. `sink` is a PS output pipe
|
|
309
466
|
// (e.g. Write-Host, or the update log pipe). Single-quoted PS literals
|
|
310
467
|
// only (npm audit #6) except the double quotes the .lnk Arguments path
|
|
@@ -313,18 +470,66 @@ exports.psq = psq;
|
|
|
313
470
|
// script to `ps()` as argv and the update swap runs it from a file. The
|
|
314
471
|
// backslash-escape `setup` used to apply was deleted with that conversion
|
|
315
472
|
// (see step 7), which is why this line no longer says "escape them".
|
|
473
|
+
// The C# rides in a single-quoted HERE-STRING (`@'` … `'@`, both alone on
|
|
474
|
+
// their line): it contains double quotes and is literal end to end, so the
|
|
475
|
+
// doubling rule above cannot be defeated by anything inside it.
|
|
316
476
|
// exported: unit-tested in test/cli.test.mjs.
|
|
317
477
|
function deskShortcutRepairPs(scriptsQ, deskDirQ, sink) {
|
|
478
|
+
const { id, icon } = lnkIdentity(`${deskDirQ}\\icon.ico`);
|
|
479
|
+
// BOTH shortcuts get the identity, and the START MENU one is the load-bearing half:
|
|
480
|
+
// deleting it from a running shell reverted the taskbar to the Electron logo with the ID
|
|
481
|
+
// still set on the process and both desktop shortcuts still carrying it (see DESKTOP_AUMID).
|
|
482
|
+
// The desktop link is kept because it is what a person double-clicks and because
|
|
483
|
+
// appids.md says to apply the ID to "all of the shortcuts" a user can launch from.
|
|
484
|
+
const lnk = (p, tag) => [
|
|
485
|
+
` 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} }`,
|
|
486
|
+
` 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} } }`,
|
|
487
|
+
];
|
|
318
488
|
return [
|
|
319
489
|
`$dLnk = Join-Path $env:PUBLIC 'Desktop\\Summrise.lnk'`,
|
|
490
|
+
// ALL-USERS Start Menu: the shortcut the shell's app resolver actually reads, and it is
|
|
491
|
+
// CREATED here when absent — a machine that never had one is exactly the machine whose
|
|
492
|
+
// taskbar shows the Electron logo, and no amount of desktop-side repair reaches it.
|
|
493
|
+
// `setup` already requires elevation (HKLM writes, Program Files), so ProgramData is
|
|
494
|
+
// writable on every path that calls this.
|
|
495
|
+
`$dSm = Join-Path $env:ProgramData 'Microsoft\\Windows\\Start Menu\\Programs\\Summrise.lnk'`,
|
|
320
496
|
`$dIco = '${deskDirQ}\\icon.ico'`,
|
|
321
497
|
`$dPs1 = '${scriptsQ}\\start-desktop.ps1'`,
|
|
498
|
+
`$dId = '${id}'`,
|
|
499
|
+
`$dRes = '${icon}'`,
|
|
500
|
+
`$dSet = $false`,
|
|
501
|
+
`$dShell = (Test-Path $dIco) -and (Test-Path $dPs1)`,
|
|
502
|
+
// Compiled when there is a SHELL to point at, not when a desktop link happens to exist:
|
|
503
|
+
// the Start Menu shortcut is created from nothing, so gating this on `$dLnk` would skip
|
|
504
|
+
// the identity on precisely the installs that need it. The `-as [type]` guard keeps a
|
|
505
|
+
// second call in one process from turning a "type already exists" throw into a silent
|
|
506
|
+
// skip of every write below.
|
|
507
|
+
`if ($dShell) {`,
|
|
508
|
+
` $dCs = @'\n${LNK_PROPS_CS}\n'@`,
|
|
509
|
+
` try { if (-not ('SummriseLnk' -as [type])) { Add-Type -TypeDefinition $dCs }; $dSet = [bool]('SummriseLnk' -as [type]) } catch { ('desk: lnk identity helper unavailable: ' + $_.Exception.Message) | ${sink} }`,
|
|
510
|
+
`}`,
|
|
322
511
|
`$dNeed = $false`,
|
|
323
512
|
`if (Test-Path $dLnk) {`,
|
|
324
513
|
` 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 }`,
|
|
514
|
+
// ...and a link that is otherwise healthy still needs repairing when its identity is
|
|
515
|
+
// missing or names a different ID/icon. `Read` returns "id|icon", so ONE comparison
|
|
516
|
+
// covers both properties — and it is the same call the verification below prints.
|
|
517
|
+
` if ($dSet) { try { if (([SummriseLnk]::Read($dLnk)) -ne ($dId + '|' + $dRes)) { $dNeed = $true } } catch { $dNeed = $true } }`,
|
|
325
518
|
`}`,
|
|
326
|
-
|
|
327
|
-
|
|
519
|
+
// The Start Menu link is created when ABSENT and rewritten when its identity is wrong —
|
|
520
|
+
// it is never deleted, and a link that already matches is left alone so a second `setup`
|
|
521
|
+
// does not churn the file.
|
|
522
|
+
`$dSmNeed = $false`,
|
|
523
|
+
`if ($dShell) {`,
|
|
524
|
+
` if (-not (Test-Path $dSm)) { $dSmNeed = $true }`,
|
|
525
|
+
` elseif (-not $dSet) { $dSmNeed = $true }`,
|
|
526
|
+
` else { try { if (([SummriseLnk]::Read($dSm)) -ne ($dId + '|' + $dRes)) { $dSmNeed = $true } } catch { $dSmNeed = $true } }`,
|
|
527
|
+
`}`,
|
|
528
|
+
`if ($dNeed -and $dShell) {`,
|
|
529
|
+
...lnk("$dLnk", "1"),
|
|
530
|
+
`}`,
|
|
531
|
+
`if ($dSmNeed -and $dShell) {`,
|
|
532
|
+
...lnk("$dSm", "2"),
|
|
328
533
|
`}`,
|
|
329
534
|
`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} } } }`,
|
|
330
535
|
];
|
|
@@ -363,7 +568,30 @@ function desktopTaskPs(installQ) {
|
|
|
363
568
|
`$dt1 = New-ScheduledTaskTrigger -AtLogOn`,
|
|
364
569
|
`$dw1 = New-ScheduledTaskTrigger -Once -At (Get-Date).AddMinutes(3) -RepetitionInterval (New-TimeSpan -Minutes 5)`,
|
|
365
570
|
`$pr = New-ScheduledTaskPrincipal -UserId ('{0}\\{1}' -f $env:USERDOMAIN, $env:USERNAME) -LogonType Interactive -RunLevel Highest`,
|
|
366
|
-
|
|
571
|
+
// NO EXECUTION TIME LIMIT -- the same value, in the same spelling, the agent's own task has
|
|
572
|
+
// carried since round 118 for the same reason: `- ExecutionTimeLimit 0 never kill the
|
|
573
|
+
// running task`. It was `-Minutes 10` here, and that WAS the defect.
|
|
574
|
+
//
|
|
575
|
+
// WHY A LIMIT KILLS A SHELL MEANT TO LIVE INDEFINITELY: this task's action is
|
|
576
|
+
// desktop-pulse.vbs -> ensure-desktop.ps1 -> start-desktop.ps1, and that last line is
|
|
577
|
+
// `& electron.exe .` -- the PowerShell CALL OPERATOR, which WAITS. The PowerShell the task
|
|
578
|
+
// launched therefore stays alive for as long as the shell does, as the shell's PARENT, and
|
|
579
|
+
// Task Scheduler enforces ExecutionTimeLimit on that process tree. Ten minutes after every
|
|
580
|
+
// start the scheduler killed the running shell; five minutes later the guarded pulse
|
|
581
|
+
// correctly brought a fresh one back; ten minutes after that it died again. The operator
|
|
582
|
+
// reported that as "反复重启", four times.
|
|
583
|
+
//
|
|
584
|
+
// NOTHING RELIED ON THE KILL, which is why removing it is the fix rather than a trade. This
|
|
585
|
+
// repository's recovery for a shell that is GONE is the guarded 5-minute pulse, and
|
|
586
|
+
// summrise-desktop-electron/src/main.ts says so from the other side, naming a logon-only task
|
|
587
|
+
// as one that "cannot recover a wedged shell". A shell that is RUNNING but wedged was never
|
|
588
|
+
// this limit's business either: the pulse's guard is a process-EXISTENCE check
|
|
589
|
+
// (`if (Get-Process electron) { exit }`), so it cannot see a wedged shell whether the limit is
|
|
590
|
+
// ten minutes or absent. `summrise update` is what heals a wedged shell, and its log says so.
|
|
591
|
+
//
|
|
592
|
+
// Measured on desktop-14rjcr8: under PT10M the four electron PIDs were replaced about every
|
|
593
|
+
// ten minutes; under PT0S the same four PIDs were still there 11 minutes later.
|
|
594
|
+
`$st = New-ScheduledTaskSettingsSet -ExecutionTimeLimit (New-TimeSpan -Seconds 0) -AllowStartIfOnBatteries -DontStopIfGoingOnBatteries -StartWhenAvailable -MultipleInstances IgnoreNew`,
|
|
367
595
|
`Register-ScheduledTask SummriseDesktop -Action $da -Trigger @($dt1,$dw1) -Principal $pr -Settings $st -Force | Out-Null`,
|
|
368
596
|
`Start-ScheduledTask -TaskName SummriseDesktop`,
|
|
369
597
|
];
|
|
@@ -2879,7 +3107,15 @@ const commands = {
|
|
|
2879
3107
|
` $da = New-ScheduledTaskAction -Execute 'wscript.exe' -Argument ('"' + $vb1 + '"') -WorkingDirectory '${q}'`,
|
|
2880
3108
|
` $dt1 = New-ScheduledTaskTrigger -AtLogOn`,
|
|
2881
3109
|
` $dw1 = New-ScheduledTaskTrigger -Once -At (Get-Date).AddMinutes(3) -RepetitionInterval (New-TimeSpan -Minutes 5)`,
|
|
2882
|
-
|
|
3110
|
+
// AND THE SETTINGS, WHICH IS THE HALF THAT REACHES A MACHINE ALREADY INSTALLED.
|
|
3111
|
+
// This call used to pass only -Action and -Trigger, and `Set-ScheduledTask` PRESERVES the
|
|
3112
|
+
// components it is not given — so an execution limit already on the task survived every
|
|
3113
|
+
// update. That is the wrong shape for a heal: `summrise update` is the only mechanism that
|
|
3114
|
+
// reaches the fleet, and a device carrying the old 10-minute limit would have kept it
|
|
3115
|
+
// forever while its log said "hardened". The value is the same one `setup` writes, so the
|
|
3116
|
+
// two cannot diverge — and installer_integrity.rs holds both ends to it.
|
|
3117
|
+
` $dst = New-ScheduledTaskSettingsSet -ExecutionTimeLimit (New-TimeSpan -Seconds 0) -AllowStartIfOnBatteries -DontStopIfGoingOnBatteries -StartWhenAvailable -MultipleInstances IgnoreNew`,
|
|
3118
|
+
` Set-ScheduledTask -TaskName 'SummriseDesktop' -Action $da -Trigger @($dt1, $dw1) -Settings $dst | Out-Null`,
|
|
2883
3119
|
` "[$(Get-Date -Format o)] desk: SummriseDesktop hardened (guarded 5-min pulse)" | ${log}`,
|
|
2884
3120
|
`}`,
|
|
2885
3121
|
`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.492",
|
|
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
|
|
@@ -47,6 +47,13 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
47
47
|
// No spawn() of summrise-agent.exe from JS — that was the d1 Chrome-OOM root
|
|
48
48
|
// cause (a bind-failed child wedged on "Press Enter to exit" forever).
|
|
49
49
|
const electron_1 = require("electron");
|
|
50
|
+
// IPC audit #6: execSync/ChildProcess were vestigial imports from the
|
|
51
|
+
// pre-async-spawn era (round-117 removed the call sites) — dropped so
|
|
52
|
+
// eslint/tsc keep the surface honest. The last one to go was the empty
|
|
53
|
+
// `import type {} from "child_process"` that stood here: it imported
|
|
54
|
+
// NOTHING (the schtasks runner reaches the module through require() with its
|
|
55
|
+
// own `as typeof import(...)` type), and a type-only import of a module the
|
|
56
|
+
// shell does not otherwise import is a "dependency" no reader can act on.
|
|
50
57
|
const path = __importStar(require("path"));
|
|
51
58
|
const fs = __importStar(require("fs"));
|
|
52
59
|
const http = __importStar(require("http"));
|
|
@@ -319,13 +326,7 @@ function buildMenu() {
|
|
|
319
326
|
{ label: "New Serial Connection…", accelerator: "CmdOrCtrl+Shift+P", click: () => sendMenu("new-serial") },
|
|
320
327
|
{ label: "New Browser Session…", accelerator: "CmdOrCtrl+Shift+B", click: () => sendMenu("new-browser") },
|
|
321
328
|
{ type: "separator" },
|
|
322
|
-
{ label: "Browser", accelerator: "CmdOrCtrl+Shift+G", click: () => sendMenu("open-browser") },
|
|
323
|
-
{ label: "Memory", accelerator: "CmdOrCtrl+Shift+M", click: () => sendMenu("open-memory") },
|
|
324
|
-
{ label: "Settings", accelerator: "CmdOrCtrl+Shift+,", click: () => sendMenu("open-settings") },
|
|
325
|
-
{ label: "Plugins", click: () => sendMenu("open-plugins") },
|
|
326
|
-
{ type: "separator" },
|
|
327
329
|
{ label: "Close Session", accelerator: "CmdOrCtrl+W", click: () => sendMenu("close-session") },
|
|
328
|
-
{ label: "Close Window", accelerator: isMac ? "Cmd+Shift+W" : "Alt+F4", role: "close" },
|
|
329
330
|
{ type: "separator" },
|
|
330
331
|
isMac ? { role: "close" } : { role: "quit", label: "Exit" },
|
|
331
332
|
],
|
|
@@ -353,8 +354,6 @@ function buildMenu() {
|
|
|
353
354
|
{ role: "resetZoom" },
|
|
354
355
|
{ role: "zoomIn" },
|
|
355
356
|
{ role: "zoomOut" },
|
|
356
|
-
{ type: "separator" },
|
|
357
|
-
{ label: "Toggle Theme", accelerator: "CmdOrCtrl+Shift+D", click: () => sendMenu("toggle-theme") },
|
|
358
357
|
{ role: "toggleDevTools" },
|
|
359
358
|
],
|
|
360
359
|
},
|
|
@@ -363,28 +362,13 @@ function buildMenu() {
|
|
|
363
362
|
submenu: [
|
|
364
363
|
{ label: "Next Session", accelerator: "Ctrl+Tab", click: () => sendMenu("next-session") },
|
|
365
364
|
{ label: "Previous Session", accelerator: "Ctrl+Shift+Tab", click: () => sendMenu("prev-session") },
|
|
366
|
-
{ type: "separator" },
|
|
367
|
-
{ label: "Export Session Log…", accelerator: "CmdOrCtrl+Shift+E", click: () => sendMenu("export-session") },
|
|
368
|
-
{ type: "separator" },
|
|
369
365
|
// IPC audit #6: the "List Sessions" entry dispatched 'list-sessions',
|
|
370
366
|
// a command with NO SPA-side handler — deleted (dead menu surface).
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
{ role: "minimize" },
|
|
377
|
-
{ role: "zoom" },
|
|
378
|
-
...(isMac ? [
|
|
379
|
-
{ type: "separator" },
|
|
380
|
-
{ role: "front" },
|
|
381
|
-
] : []),
|
|
382
|
-
],
|
|
383
|
-
},
|
|
384
|
-
{
|
|
385
|
-
role: "help",
|
|
386
|
-
submenu: [
|
|
387
|
-
{ label: "Summrise Agent Status", click: () => { focusMain(); sendMenu("show-status"); } },
|
|
367
|
+
// The "Export Session Log…" entry (^⇧E) was deleted for the same kind of
|
|
368
|
+
// reason read the other way: the per-tab export mark IS the action
|
|
369
|
+
// (TabBar's .tab-export, one mark per session, so it exports what you
|
|
370
|
+
// point at rather than whatever happens to be active). The accelerator
|
|
371
|
+
// is still handled by the SPA's own keydown map in a plain browser.
|
|
388
372
|
],
|
|
389
373
|
},
|
|
390
374
|
];
|
|
@@ -671,7 +655,9 @@ function embeddedRecover() {
|
|
|
671
655
|
return { ok: false };
|
|
672
656
|
// Already decided by loadTarget() when embeddedUrl was set (the ONE load
|
|
673
657
|
// door) — recovery re-loads that decision, it does not re-open the policy.
|
|
674
|
-
|
|
658
|
+
// The fallback is about:blank, NOT a third-party home page: recovering a
|
|
659
|
+
// crashed view must not make a request to a search engine to do it.
|
|
660
|
+
const url = embeddedUrl || "about:blank";
|
|
675
661
|
view.webContents.loadURL(url).catch(() => { });
|
|
676
662
|
// Re-show if the SPA slot is live (bounds were placed before the crash).
|
|
677
663
|
if (embeddedVisible && win && embeddedBounds) {
|
|
@@ -906,12 +892,38 @@ const httpServer = http.createServer((req, res) => {
|
|
|
906
892
|
}
|
|
907
893
|
});
|
|
908
894
|
if (gotTheLock) {
|
|
909
|
-
//
|
|
910
|
-
//
|
|
895
|
+
// TASKBAR GROUPING AND THE TASKBAR GROUP'S ICON — HALF ONE OF TWO, AND THIS IS THE HALF
|
|
896
|
+
// THAT IS A PRECONDITION RATHER THAN A HINT.
|
|
897
|
+
// Microsoft's rule for System.AppUserModel.RelaunchIconResource is a three-step lookup:
|
|
898
|
+
// the property ON THE WINDOW; else "the system attempts to find a shortcut with the same
|
|
899
|
+
// AppUserModelID, and pins that shortcut to the taskbar to represent the window"; else
|
|
900
|
+
// "the backing executable of the process that owns it is used". Which step is reachable
|
|
901
|
+
// at all depends on THIS call — the same page: the shortcut lookup happens only "If an
|
|
902
|
+
// explicit AppUserModelID is set on the window", and without one the property "is ignored
|
|
903
|
+
// and the window is grouped and pinned as if it were part of its owning process".
|
|
904
|
+
// This shell is launched UNPACKAGED (`electron.exe .` out of the staged runtime), so
|
|
905
|
+
// step three is stock electron.exe — the Electron logo. Removing this call (d4edbb64)
|
|
906
|
+
// could not fix that: it deleted the caller's half of an association whose OTHER half is
|
|
907
|
+
// the shortcut, which left step three as the only reachable step in BOTH states. The
|
|
908
|
+
// shortcut half is written in agent/summrise-agent-npm/src/summrise.ts (see DESKTOP_AUMID
|
|
909
|
+
// there) as System.AppUserModel.ID — the same string — plus RelaunchIconResource pointing
|
|
910
|
+
// at the sunrise .ico. KEEP THE TWO IN STEP; the literal below is a copy because this file
|
|
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.
|
|
919
|
+
// (The same shortcut association is also the documented precondition for the hide-to-tray
|
|
920
|
+
// toast below: Electron requires a Start Menu shortcut carrying the ID and a
|
|
921
|
+
// ToastActivatorCLSID, and this one carries neither — unchanged by this call.)
|
|
922
|
+
const AUMID = "online.saisi.summrise.desktop";
|
|
911
923
|
try {
|
|
912
924
|
if (process.platform === "win32")
|
|
913
|
-
electron_1.app.setAppUserModelId(
|
|
914
|
-
iconReport["appUserModelId"] = process.platform === "win32" ?
|
|
925
|
+
electron_1.app.setAppUserModelId(AUMID);
|
|
926
|
+
iconReport["appUserModelId"] = process.platform === "win32" ? AUMID : "(non-windows)";
|
|
915
927
|
}
|
|
916
928
|
catch {
|
|
917
929
|
iconReport["appUserModelId"] = "(set-failed)";
|
|
@@ -934,6 +946,16 @@ if (gotTheLock) {
|
|
|
934
946
|
electron_1.Menu.setApplicationMenu(buildMenu());
|
|
935
947
|
win = new electron_1.BrowserWindow({
|
|
936
948
|
width: 1200, height: 800, title: "Summrise",
|
|
949
|
+
// NO WHITE FLASH (window-creation honesty, round-275). The window used to be
|
|
950
|
+
// created VISIBLE, before loadDesktop() runs its ~800 ms /api/status probe and
|
|
951
|
+
// before the first loadURL — so every launch painted a blank white frame and then
|
|
952
|
+
// whatever the agent turned out to be. show:false keeps it off screen until the
|
|
953
|
+
// first document has painted (ready-to-show, below); backgroundColor paints that
|
|
954
|
+
// first frame in the shell's own ink instead of Chromium's white, which is the one
|
|
955
|
+
// of the two documents that had NOT already fixed this (WAIT_HTML hardcodes
|
|
956
|
+
// background:#111, and the SPA's chrome is dark).
|
|
957
|
+
show: false,
|
|
958
|
+
backgroundColor: "#111",
|
|
937
959
|
// Taskbar + title-bar icon: without this Windows shows the stock
|
|
938
960
|
// electron.exe icon (the running binary is stock Electron). PNG —
|
|
939
961
|
// see windowIcon() on why not .ico here.
|
|
@@ -955,6 +977,11 @@ if (gotTheLock) {
|
|
|
955
977
|
backgroundThrottling: false,
|
|
956
978
|
},
|
|
957
979
|
});
|
|
980
|
+
// The other half of show:false — the first paint shows the window. It fires on the
|
|
981
|
+
// first document to paint (the SPA when the agent answers, the wait page when it does
|
|
982
|
+
// not), and the handler is registered in the same tick as the constructor, before any
|
|
983
|
+
// loadURL can happen, so there is no window in which the event is missed.
|
|
984
|
+
win.once("ready-to-show", () => { win?.show(); });
|
|
958
985
|
// review #6 (MED) TOP RISK: the main window carries the summriseDesktop/
|
|
959
986
|
// summriseBrowser preload bridge (setAutoLaunch → schtasks /create …
|
|
960
987
|
// -File <ps1>, browser-session:open). Without a navigation veto a
|
|
@@ -1048,25 +1075,15 @@ if (gotTheLock) {
|
|
|
1048
1075
|
let retryMs = 2000;
|
|
1049
1076
|
const nextRetry = () => { retryMs = Math.min(retryMs * 2, 30000); return retryMs; };
|
|
1050
1077
|
const resetRetry = () => { retryMs = 2000; };
|
|
1051
|
-
|
|
1052
|
-
|
|
1053
|
-
|
|
1054
|
-
|
|
1055
|
-
|
|
1056
|
-
|
|
1057
|
-
|
|
1058
|
-
|
|
1059
|
-
|
|
1060
|
-
clearTimeout(t);
|
|
1061
|
-
if (r.ok) {
|
|
1062
|
-
const j = await r.json();
|
|
1063
|
-
const v = j.release || j.version;
|
|
1064
|
-
if (v)
|
|
1065
|
-
win?.setTitle(`Summrise — v${v}`);
|
|
1066
|
-
}
|
|
1067
|
-
}
|
|
1068
|
-
catch { /* agent down — keep default title */ }
|
|
1069
|
-
};
|
|
1078
|
+
// NO setVersionTitle() HERE, DELETED. It fetched /api/status with the device token and
|
|
1079
|
+
// called win.setTitle("Summrise — v1.2.x"), and that title NEVER REACHED THE SCREEN: the
|
|
1080
|
+
// SPA owns document.title (`useAttentionTitle` assigns it on every attention change,
|
|
1081
|
+
// panel-react/src/hooks/useAttention.ts:96 — plain "Summrise Agent" in the desktop window)
|
|
1082
|
+
// and no page-title-updated handler on THIS window stops it, so the renderer's title
|
|
1083
|
+
// replaced the native one on the first render. The version it wanted to show is already on
|
|
1084
|
+
// screen, in the panel's own status bar (DesktopShell's .desktop-status) and in the tray
|
|
1085
|
+
// tooltip, both of which read the same /api/status. A native title that only the process
|
|
1086
|
+
// could read was also the LAST credentialed fetch on this path — the tray keeps its own.
|
|
1070
1087
|
// stage-n: SELF-HEAL watchdog. The wait-page button needed a human, but
|
|
1071
1088
|
// when the AGENT is down the cloudflared tunnel (agent-supervised) dies
|
|
1072
1089
|
// too — a remote operator then has NO channel left to click anything
|
|
@@ -1085,7 +1102,6 @@ if (gotTheLock) {
|
|
|
1085
1102
|
if (await agentReady()) {
|
|
1086
1103
|
agentMissCount = 0;
|
|
1087
1104
|
resetRetry();
|
|
1088
|
-
setVersionTitle();
|
|
1089
1105
|
win?.loadURL(`${(0, url_policy_1.agentBase)()}/desktop/`).catch(() => { });
|
|
1090
1106
|
}
|
|
1091
1107
|
else {
|
|
@@ -1125,11 +1141,13 @@ if (gotTheLock) {
|
|
|
1125
1141
|
// without it, playwright grabbed the MAIN window (now tripwired) or the
|
|
1126
1142
|
// user had to open the Browser page first. The view is hidden until the
|
|
1127
1143
|
// SPA's Browser page reports bounds (embeddedViewPlace shows it).
|
|
1144
|
+
// NO EAGER NAVIGATION: this used to loadURL("https://www.bing.com"), i.e. a
|
|
1145
|
+
// request to a third party on EVERY launch before anyone asked for a page.
|
|
1146
|
+
// The view is still created — about:blank is a first-class CDP target (the
|
|
1147
|
+
// reason it exists here is that a target be available, not that a page be
|
|
1148
|
+
// loaded), and the user/AI navigates it next (loadTarget is the one door).
|
|
1128
1149
|
try {
|
|
1129
|
-
|
|
1130
|
-
if (v && !v.webContents.isDestroyed()) {
|
|
1131
|
-
v.webContents.loadURL("https://www.bing.com").catch(() => { });
|
|
1132
|
-
}
|
|
1150
|
+
embeddedViewEnsure();
|
|
1133
1151
|
}
|
|
1134
1152
|
catch { /* non-fatal */ }
|
|
1135
1153
|
console.log(`[summrise] CDP endpoint: http://127.0.0.1:${CDP_PORT} (playwright connectOverCDP)`);
|
|
@@ -1195,6 +1213,9 @@ if (gotTheLock) {
|
|
|
1195
1213
|
// status strip shows the same pair).
|
|
1196
1214
|
let trayAgentCpu = null;
|
|
1197
1215
|
let trayAgentMem = null;
|
|
1216
|
+
/** WHEN /api/status last ANSWERED (null = it never has since launch) — the tray's
|
|
1217
|
+
* tooltip reports this instead of asserting a state it cannot date. */
|
|
1218
|
+
let trayLastAnsweredAt = null;
|
|
1198
1219
|
/** "3m 24s" / "1h 5m" / "2d 3h" — compact uptime for the tray. */
|
|
1199
1220
|
const fmtUptime = (secs) => {
|
|
1200
1221
|
if (secs < 60)
|
|
@@ -1205,10 +1226,16 @@ if (gotTheLock) {
|
|
|
1205
1226
|
return `${Math.floor(secs / 3600)}h ${Math.floor((secs % 3600) / 60)}m`;
|
|
1206
1227
|
return `${Math.floor(secs / 86400)}d ${Math.floor((secs % 86400) / 3600)}h`;
|
|
1207
1228
|
};
|
|
1229
|
+
/** "14:02:31" local — the WHEN of an observation, for the one surface that cannot
|
|
1230
|
+
* re-derive it (a tray tooltip is a string set once per poll, not a render). */
|
|
1231
|
+
const fmtClock = (at) => new Date(at).toTimeString().slice(0, 8);
|
|
1208
1232
|
const refreshTray = async () => {
|
|
1209
1233
|
// HTTP liveness, not the TCP probe (see agentReady above): a wedged-
|
|
1210
1234
|
// but-listening agent must show as STOPPED, not "running".
|
|
1211
1235
|
const running = await agentResponds(1000);
|
|
1236
|
+
const observedAt = Date.now();
|
|
1237
|
+
if (running)
|
|
1238
|
+
trayLastAnsweredAt = observedAt;
|
|
1212
1239
|
let version = trayAgentVersion;
|
|
1213
1240
|
let uptime = trayAgentUptime;
|
|
1214
1241
|
let sessions = trayAgentSessions;
|
|
@@ -1248,9 +1275,19 @@ if (gotTheLock) {
|
|
|
1248
1275
|
const vitals = running && mem !== null
|
|
1249
1276
|
? `, CPU ${cpu === null ? "?" : Math.round(cpu)}% · MEM ${Math.round(mem)}%`
|
|
1250
1277
|
: "";
|
|
1278
|
+
// THE TOOLTIP NAMES THE OBSERVATION, NOT A VERDICT (round-275). It used to print
|
|
1279
|
+
// "Agent stopped" as present tense from ONE un-timestamped probe, recomputed every
|
|
1280
|
+
// 30 s — half a minute stale while reading as now, the shape the panel's liveness
|
|
1281
|
+
// strings were already fixed for (`checked 12s ago`, `reachability NOT VERIFIED`).
|
|
1282
|
+
// The WHEN is a CLOCK rather than "answered 12s ago", deliberately: a tooltip is a
|
|
1283
|
+
// string written once per poll and Electron gives it no way to recompute, so a
|
|
1284
|
+
// relative age would freeze at the value it had when written and read as "now" for
|
|
1285
|
+
// the next 30 s — the very failure this line removes. A timestamp does not decay.
|
|
1251
1286
|
const health = running
|
|
1252
|
-
? `
|
|
1253
|
-
:
|
|
1287
|
+
? `answered ${fmtClock(observedAt)} · v${version || "?"}${uptime ? `, up ${uptime}` : ""}${sessions ? `, ${sessions} session${sessions === 1 ? "" : "s"}` : ""}${vitals}`
|
|
1288
|
+
: trayLastAnsweredAt === null
|
|
1289
|
+
? "reachability NOT VERIFIED · no reply since launch"
|
|
1290
|
+
: `not answering · last reply ${fmtClock(trayLastAnsweredAt)}`;
|
|
1254
1291
|
// stage-n: the agent died WHILE the window was showing the SPA (the
|
|
1255
1292
|
// wait page only renders when the boot probe fails). Swap the window
|
|
1256
1293
|
// to the wait page — its "Start Agent" button is now actually visible
|
|
@@ -1266,16 +1303,15 @@ if (gotTheLock) {
|
|
|
1266
1303
|
if (running && agentWatchActive)
|
|
1267
1304
|
agentWatchActive = false;
|
|
1268
1305
|
tray.setToolTip(`Summrise — ${health}`);
|
|
1306
|
+
// THE FOUR "New …" ROWS ARE GONE (round-275): they duplicated the desktop
|
|
1307
|
+
// header's `+ New` menu item for item (pty/ssh/serial/browser), one click
|
|
1308
|
+
// cheaper than the tray path, which had to restore and focus the window
|
|
1309
|
+
// first anyway. "Open" is the tray's job; creating sessions is the app's.
|
|
1269
1310
|
tray.setContextMenu(electron_1.Menu.buildFromTemplate([
|
|
1270
1311
|
{ label: "Open", click: () => { focusMain(); } },
|
|
1271
1312
|
{ type: "separator" },
|
|
1272
1313
|
{ label: health, enabled: false },
|
|
1273
1314
|
{ type: "separator" },
|
|
1274
|
-
{ label: "New Terminal", click: () => { focusMain(); sendMenu("new-pty"); } },
|
|
1275
|
-
{ label: "New SSH…", click: () => { focusMain(); sendMenu("new-ssh"); } },
|
|
1276
|
-
{ label: "New Serial…", click: () => { focusMain(); sendMenu("new-serial"); } },
|
|
1277
|
-
{ label: "New Browser…", click: () => { focusMain(); sendMenu("new-browser"); } },
|
|
1278
|
-
{ type: "separator" },
|
|
1279
1315
|
{ label: "Quit", click: () => { electron_1.app.isQuitting = true; electron_1.app.quit(); } },
|
|
1280
1316
|
]));
|
|
1281
1317
|
};
|