local-operator-ui 0.25.3 → 0.25.5

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.
Files changed (71) hide show
  1. package/README.md +27 -0
  2. package/out/main/index.js +567 -216
  3. package/out/renderer/assets/{_basePickBy-CUI9MZ4f.js → _basePickBy-CU6cbfV-.js} +1 -1
  4. package/out/renderer/assets/{_baseUniq-DsrAy52N.js → _baseUniq-3eW3-sWy.js} +1 -1
  5. package/out/renderer/assets/{agent-details-page-Br7O8DtR.js → agent-details-page-B9tJtnsB.js} +1 -1
  6. package/out/renderer/assets/{agent-hub-page-Cm0yzEub.js → agent-hub-page-DzfzgwPB.js} +1 -1
  7. package/out/renderer/assets/{agents-page-BBfNcLHk.js → agents-page-BvOGxAAb.js} +2 -2
  8. package/out/renderer/assets/{arc-CUR3ZFhl.js → arc-2_87zcwb.js} +1 -1
  9. package/out/renderer/assets/{architectureDiagram-IEHRJDOE-DOji4S3Q.js → architectureDiagram-IEHRJDOE-Dfs7RsUV.js} +1 -1
  10. package/out/renderer/assets/{arrow-left-DnuA26Fd.js → arrow-left-CcktVilP.js} +1 -1
  11. package/out/renderer/assets/{blockDiagram-JOT3LUYC-C9r-1qgy.js → blockDiagram-JOT3LUYC-DNwa0Hfv.js} +1 -1
  12. package/out/renderer/assets/{browser-page-Cq_rvMea.js → browser-page-CBufKj--.js} +1 -1
  13. package/out/renderer/assets/{c4Diagram-VJAJSXHY-CgVXRHMX.js → c4Diagram-VJAJSXHY-KeZkxF7Y.js} +1 -1
  14. package/out/renderer/assets/channel-D3Qk6DSD.js +1 -0
  15. package/out/renderer/assets/{chunk-4BMEZGHF-BCCDYlHx.js → chunk-4BMEZGHF-BTCRyTKZ.js} +1 -1
  16. package/out/renderer/assets/{chunk-A2AXSNBT-DQv_q1F4.js → chunk-A2AXSNBT-DLMWYufm.js} +1 -1
  17. package/out/renderer/assets/{chunk-AEK57VVT-uiKrJ6rs.js → chunk-AEK57VVT-DIOpHfUC.js} +1 -1
  18. package/out/renderer/assets/{chunk-D6G4REZN-DGNuSfnq.js → chunk-D6G4REZN-3kvxiifv.js} +1 -1
  19. package/out/renderer/assets/{chunk-RZ5BOZE2-CZOZ0qYQ.js → chunk-RZ5BOZE2-DNWczlot.js} +1 -1
  20. package/out/renderer/assets/{chunk-XZIHB7SX-LBl420Pq.js → chunk-XZIHB7SX-x5aN2jja.js} +1 -1
  21. package/out/renderer/assets/classDiagram-GIVACNV2-SA9scKTw.js +1 -0
  22. package/out/renderer/assets/classDiagram-v2-COTLJTTW-SA9scKTw.js +1 -0
  23. package/out/renderer/assets/clone-Bl4GsLEn.js +1 -0
  24. package/out/renderer/assets/{compact-pagination-CRUt_cmC.js → compact-pagination-DKAy-IR2.js} +1 -1
  25. package/out/renderer/assets/{dagre-OKDRZEBW-BvGSIi2I.js → dagre-OKDRZEBW-CDUKiMzY.js} +1 -1
  26. package/out/renderer/assets/{diagram-SSKATNLV-47O2muUk.js → diagram-SSKATNLV-B2kH_yx8.js} +1 -1
  27. package/out/renderer/assets/{diagram-VNBRO52H-CG4_K9jG.js → diagram-VNBRO52H-jfKWU-Lm.js} +1 -1
  28. package/out/renderer/assets/{erDiagram-Q7BY3M3F-BKPT4Ger.js → erDiagram-Q7BY3M3F-7XQUHMG0.js} +1 -1
  29. package/out/renderer/assets/{flowDiagram-4HSFHLVR-C6OqF7rj.js → flowDiagram-4HSFHLVR-TzsaCK4n.js} +1 -1
  30. package/out/renderer/assets/{ganttDiagram-APWFNJXF-BsAxR8C1.js → ganttDiagram-APWFNJXF-yehmMRqe.js} +1 -1
  31. package/out/renderer/assets/{gitGraphDiagram-7IBYFJ6S-Jy7DxrgG.js → gitGraphDiagram-7IBYFJ6S-BjJi9W8H.js} +1 -1
  32. package/out/renderer/assets/{graph-BZYPzyg2.js → graph-qAJoLSKS.js} +1 -1
  33. package/out/renderer/assets/icon-DU3qS4xX.css +1 -0
  34. package/out/renderer/assets/{index-RZD44-S3.js → index-B59GZTl_.js} +1 -1
  35. package/out/renderer/assets/{index-DVeeuYz1.js → index-CRN0i7I1.js} +1 -1
  36. package/out/renderer/assets/{index-CFct9cqp.js → index-Du_04xLY.js} +356 -356
  37. package/out/renderer/assets/{infoDiagram-PH2N3AL5-dmY9mvLF.js → infoDiagram-PH2N3AL5--ktj13x4.js} +1 -1
  38. package/out/renderer/assets/{installer-BUekjhed.js → installer-B_gSn64f.js} +1 -1
  39. package/out/renderer/assets/{journeyDiagram-U35MCT3I-XmmyNpv5.js → journeyDiagram-U35MCT3I-BKVaP-gO.js} +1 -1
  40. package/out/renderer/assets/{kanban-definition-NDS4AKOZ-BVCPlSpm.js → kanban-definition-NDS4AKOZ-CeSOSCcU.js} +1 -1
  41. package/out/renderer/assets/{layout-CxeJgGG0.js → layout-D2Cfz3aw.js} +1 -1
  42. package/out/renderer/assets/{legacy-agents-page-DNgwgCyB.js → legacy-agents-page-5_OwD2vD.js} +1 -1
  43. package/out/renderer/assets/{mermaid.core-CXWbTsAT.js → mermaid.core-DsqDVkGt.js} +5 -5
  44. package/out/renderer/assets/{mindmap-definition-ALO5MXBD-B5EFMpzK.js → mindmap-definition-ALO5MXBD-cXlbqLJn.js} +1 -1
  45. package/out/renderer/assets/{page-header-M5DAJreE.js → page-header-ehyv1ujj.js} +1 -1
  46. package/out/renderer/assets/{parseISO-CDh833Jr.js → parseISO-CHtHZJU3.js} +1 -1
  47. package/out/renderer/assets/{pieDiagram-IB7DONF6-Ccb705k-.js → pieDiagram-IB7DONF6-BnYWJ3Cc.js} +1 -1
  48. package/out/renderer/assets/{quadrantDiagram-7GDLP6J5-KRot01vw.js → quadrantDiagram-7GDLP6J5-DzGWwKDC.js} +1 -1
  49. package/out/renderer/assets/{radar-MK3ICKWK-w1jSyppR.js → radar-MK3ICKWK-Cz8qP9DC.js} +1 -1
  50. package/out/renderer/assets/{radient-auth-buttons-D6VoYc_3.js → radient-auth-buttons-mhEV8xEj.js} +1 -1
  51. package/out/renderer/assets/{requirementDiagram-KVF5MWMF-D0OxQxeF.js → requirementDiagram-KVF5MWMF-Cx-mDTR6.js} +1 -1
  52. package/out/renderer/assets/{sankeyDiagram-QLVOVGJD-Z4AIMbdQ.js → sankeyDiagram-QLVOVGJD-Bc1DOPhd.js} +1 -1
  53. package/out/renderer/assets/{schedules-page-CA-Rg3H-.js → schedules-page-DWV64xxK.js} +1 -1
  54. package/out/renderer/assets/{sequenceDiagram-X6HHIX6F-DSrAwJWQ.js → sequenceDiagram-X6HHIX6F-CIN-8sm7.js} +1 -1
  55. package/out/renderer/assets/{settings-page-B8PPcN6r.js → settings-page-vtL6TaYz.js} +1 -1
  56. package/out/renderer/assets/{shield-check-Do9ljoiZ.js → shield-check-Biv7u3YI.js} +1 -1
  57. package/out/renderer/assets/{stateDiagram-DGXRK772-BPeN9LWy.js → stateDiagram-DGXRK772-DVmGEm-f.js} +1 -1
  58. package/out/renderer/assets/stateDiagram-v2-YXO3MK2T-Cr8PVgPH.js +1 -0
  59. package/out/renderer/assets/{timeline-definition-BDJGKUSR-CcUnQn74.js → timeline-definition-BDJGKUSR-B1LptBCI.js} +1 -1
  60. package/out/renderer/assets/{use-agent-like-mutation-BXm60QBg.js → use-agent-like-mutation-HFVpi3nG.js} +1 -1
  61. package/out/renderer/assets/{xychartDiagram-VJFVF3MP-DOHYtSG5.js → xychartDiagram-VJFVF3MP-CB_p7m3M.js} +1 -1
  62. package/out/renderer/index.html +3 -3
  63. package/out/renderer/installer.html +4 -4
  64. package/package.json +4 -3
  65. package/out/renderer/assets/channel-BWq61nTi.js +0 -1
  66. package/out/renderer/assets/classDiagram-GIVACNV2-DVldbDH9.js +0 -1
  67. package/out/renderer/assets/classDiagram-v2-COTLJTTW-DVldbDH9.js +0 -1
  68. package/out/renderer/assets/clone-BzYaT6pf.js +0 -1
  69. package/out/renderer/assets/icon-TFBsh9Po.css +0 -1
  70. package/out/renderer/assets/stateDiagram-v2-YXO3MK2T-B8kJy16V.js +0 -1
  71. /package/out/renderer/assets/{icon-CZlr9p2L.js → icon-hwUlaPPO.js} +0 -0
package/out/main/index.js CHANGED
@@ -13438,21 +13438,377 @@ function registerDesktopIPC(window, expectedUrl, request, streams, media, notifi
13438
13438
  });
13439
13439
  }
13440
13440
  }
13441
- function presentWindow(window, show) {
13441
+ const WINDOW_MODES = ["normal", "headless", "inactive"];
13442
+ const WINDOW_MODE_ENV = "LOCAL_OPERATOR_UI_WINDOW_MODE";
13443
+ const WINDOW_SIZE_ENV = "LOCAL_OPERATOR_UI_WINDOW_SIZE";
13444
+ const WINDOW_MODE_FLAG = "--window-mode";
13445
+ const WINDOW_SIZE_FLAG = "--window-size";
13446
+ const AGENT_LAUNCH_FLAGS = [
13447
+ "--user-data-dir",
13448
+ "--remote-debugging-port"
13449
+ ];
13450
+ const LAUNCHER_KEEP_ALIVE_ENV = "LOCAL_OPERATOR_UI_HEADLESS_KEEP_ALIVE";
13451
+ const LAUNCHER_POLL_INTERVAL_MS = 2e3;
13452
+ const LAUNCHER_EXIT_DEADLINE_MS = 1e4;
13453
+ const WINDOW_MIN_WIDTH = 800;
13454
+ const WINDOW_MIN_HEIGHT = 600;
13455
+ const WINDOW_MAX_EDGE = 16384;
13456
+ const DEFAULT_WINDOW_WIDTH = 1380;
13457
+ const DEFAULT_WINDOW_HEIGHT = 900;
13458
+ const WINDOW_BEHAVIOUR = {
13459
+ normal: {
13460
+ show: "focus",
13461
+ focusable: true,
13462
+ backgroundThrottling: true,
13463
+ hideDock: false
13464
+ },
13465
+ inactive: {
13466
+ show: "inactive",
13467
+ focusable: true,
13468
+ backgroundThrottling: false,
13469
+ // Visible on purpose, so it keeps its tile: a run somebody wants to
13470
+ // watch has to be findable in the Dock.
13471
+ hideDock: false
13472
+ },
13473
+ headless: {
13474
+ show: "never",
13475
+ focusable: false,
13476
+ backgroundThrottling: false,
13477
+ hideDock: true
13478
+ }
13479
+ };
13480
+ function readFlag(argv, name) {
13481
+ let found = false;
13482
+ let valueless = false;
13483
+ let value;
13484
+ for (let index = 0; index < argv.length; index += 1) {
13485
+ const argument = argv[index];
13486
+ if (argument === name) {
13487
+ const next = argv[index + 1];
13488
+ found = true;
13489
+ if (next === void 0 || next.startsWith("--")) {
13490
+ valueless = true;
13491
+ continue;
13492
+ }
13493
+ value = next;
13494
+ continue;
13495
+ }
13496
+ if (argument.startsWith(`${name}=`)) {
13497
+ found = true;
13498
+ value = argument.slice(name.length + 1);
13499
+ }
13500
+ }
13501
+ return { found, value, valueless };
13502
+ }
13503
+ function parseWindowMode(value) {
13504
+ if (value === void 0) return null;
13505
+ const normalized = value.trim().toLowerCase();
13506
+ return WINDOW_MODES.includes(normalized) ? normalized : null;
13507
+ }
13508
+ const WINDOW_SIZE_PATTERN = /^(\d+)\s*[x×]\s*(\d+)$/i;
13509
+ function parseWindowSize(value) {
13510
+ if (value === void 0) return null;
13511
+ const match = value.trim().match(WINDOW_SIZE_PATTERN);
13512
+ if (!match) return null;
13513
+ const width = Number(match[1]);
13514
+ const height = Number(match[2]);
13515
+ if (width <= 0 || height <= 0) return null;
13516
+ return { width, height };
13517
+ }
13518
+ function resolveWindowLaunchPlan(input = {}) {
13519
+ const env = input.env ?? {};
13520
+ const argv = input.argv ?? [];
13521
+ const problems = [];
13522
+ const modeFlag = readFlag(argv, WINDOW_MODE_FLAG);
13523
+ const modeValue = modeFlag.found ? modeFlag.value : env[WINDOW_MODE_ENV];
13524
+ const modeRaw = typeof modeValue === "string" && modeValue.trim() === "" ? void 0 : modeValue;
13525
+ const parsedMode = parseWindowMode(modeRaw);
13526
+ const unnamed = !modeFlag.found && modeRaw === void 0;
13527
+ const agentFlag = unnamed ? AGENT_LAUNCH_FLAGS.find((flag) => readFlag(argv, flag).found) : void 0;
13528
+ const platform = input.platform ?? process.platform;
13529
+ const driven = unnamed && agentFlag === void 0 && input.packaged === false && platform !== "win32" && input.stdinIsTTY !== true && input.stdoutIsTTY !== true;
13530
+ const assumed = agentFlag ? `${agentFlag} marks an agent-driven launch, and no window mode was named` : driven ? "the launch is not a packaged app and has no terminal on either stream, so it is a driven run rather than the operator using the app" : null;
13531
+ if (modeFlag.found && modeFlag.value === void 0) {
13532
+ problems.push(
13533
+ `${WINDOW_MODE_FLAG} needs a value: ${WINDOW_MODES.join("|")}`
13534
+ );
13535
+ } else if (modeFlag.valueless) {
13536
+ problems.push(
13537
+ `${WINDOW_MODE_FLAG} was given with no value; keeping "${modeRaw}"`
13538
+ );
13539
+ } else if (modeValue !== void 0 && modeRaw === void 0) {
13540
+ problems.push(
13541
+ `${modeFlag.found ? WINDOW_MODE_FLAG : WINDOW_MODE_ENV} names no mode (empty value)${assumed ? "" : "; using normal"}`
13542
+ );
13543
+ } else if (parsedMode === null && modeRaw !== void 0) {
13544
+ problems.push(
13545
+ `${modeFlag.found ? WINDOW_MODE_FLAG : WINDOW_MODE_ENV}="${modeRaw}" is not one of ${WINDOW_MODES.join("|")}; using normal`
13546
+ );
13547
+ }
13548
+ const mode = parsedMode ?? (assumed ? "headless" : "normal");
13549
+ const sizeFlag = readFlag(argv, WINDOW_SIZE_FLAG);
13550
+ const sizeRaw = sizeFlag.found ? sizeFlag.value : env[WINDOW_SIZE_ENV];
13551
+ const parsedSize = parseWindowSize(sizeRaw);
13552
+ if (sizeFlag.found && sizeFlag.value === void 0) {
13553
+ problems.push(`${WINDOW_SIZE_FLAG} needs a value: <width>x<height>`);
13554
+ } else if (sizeFlag.valueless) {
13555
+ problems.push(
13556
+ `${WINDOW_SIZE_FLAG} was given with no value; keeping "${String(sizeRaw)}"`
13557
+ );
13558
+ } else if (parsedSize === null && sizeRaw !== void 0) {
13559
+ problems.push(
13560
+ `${sizeFlag.found ? WINDOW_SIZE_FLAG : WINDOW_SIZE_ENV}="${sizeRaw}" is not <width>x<height>; using ${DEFAULT_WINDOW_WIDTH}x${DEFAULT_WINDOW_HEIGHT}`
13561
+ );
13562
+ }
13563
+ const requested = parsedSize ?? {
13564
+ width: DEFAULT_WINDOW_WIDTH,
13565
+ height: DEFAULT_WINDOW_HEIGHT
13566
+ };
13567
+ const width = Math.min(
13568
+ Math.max(requested.width, WINDOW_MIN_WIDTH),
13569
+ WINDOW_MAX_EDGE
13570
+ );
13571
+ const height = Math.min(
13572
+ Math.max(requested.height, WINDOW_MIN_HEIGHT),
13573
+ WINDOW_MAX_EDGE
13574
+ );
13575
+ if (width !== requested.width || height !== requested.height) {
13576
+ problems.push(
13577
+ `window size ${requested.width}x${requested.height} clamped to ${width}x${height} (floor ${WINDOW_MIN_WIDTH}x${WINDOW_MIN_HEIGHT}, ceiling ${WINDOW_MAX_EDGE})`
13578
+ );
13579
+ }
13580
+ return {
13581
+ mode,
13582
+ assumed,
13583
+ ...WINDOW_BEHAVIOUR[mode],
13584
+ width,
13585
+ height,
13586
+ problems
13587
+ };
13588
+ }
13589
+ const WINDOW_INTENT_KEY = "localOperatorWindowMode";
13590
+ function windowIntentPayload(mode, requester = {}) {
13591
+ return {
13592
+ [WINDOW_INTENT_KEY]: {
13593
+ mode,
13594
+ ...requester.pid === void 0 ? {} : { pid: requester.pid },
13595
+ ...requester.cwd === void 0 ? {} : { cwd: requester.cwd }
13596
+ }
13597
+ };
13598
+ }
13599
+ const MAX_DECLARED_CWD = 200;
13600
+ function breaksALine(character) {
13601
+ const point = character.codePointAt(0) ?? 0;
13602
+ return point <= 31 || point === 127 || point >= 128 && point <= 159 || point === 8232 || point === 8233;
13603
+ }
13604
+ function declaredField(value) {
13605
+ if (typeof value !== "string" || value === "") return void 0;
13606
+ const flat = [...value].map((character) => breaksALine(character) ? " " : character).join("").trim();
13607
+ if (flat === "") return void 0;
13608
+ return flat.length > MAX_DECLARED_CWD ? `${flat.slice(0, MAX_DECLARED_CWD)}...` : flat;
13609
+ }
13610
+ function readWindowIntent(additionalData) {
13611
+ if (typeof additionalData !== "object" || additionalData === null)
13612
+ return null;
13613
+ const value = additionalData[WINDOW_INTENT_KEY];
13614
+ if (typeof value === "string") {
13615
+ const mode2 = parseWindowMode(value);
13616
+ return mode2 === null ? null : { mode: mode2 };
13617
+ }
13618
+ if (typeof value !== "object" || value === null) return null;
13619
+ const fields = value;
13620
+ const mode = typeof fields.mode === "string" ? parseWindowMode(fields.mode) : null;
13621
+ if (mode === null) return null;
13622
+ return {
13623
+ mode,
13624
+ /*
13625
+ * A pid is a safe integer or it is not a pid. The DECLARED fields are made
13626
+ * safe for a line-oriented log (review round 2, NIT-2): the payload is
13627
+ * whatever the losing process chose to say about itself, and a `cwd` with a
13628
+ * newline in it — legal on POSIX — would forge a second `[window-raise]` line.
13629
+ * Nothing is ever DECIDED from either field, which is why the validation is
13630
+ * about the log rather than about trust.
13631
+ */
13632
+ ...typeof fields.pid === "number" && Number.isSafeInteger(fields.pid) ? { pid: fields.pid } : {},
13633
+ ...declaredField(fields.cwd) === void 0 ? {} : { cwd: declaredField(fields.cwd) }
13634
+ };
13635
+ }
13636
+ function resolveSecondLaunchShow(input) {
13637
+ const carried = readWindowIntent(input.additionalData);
13638
+ if (carried !== null) return WINDOW_BEHAVIOUR[carried.mode].show;
13639
+ return resolveWindowLaunchPlan({ argv: input.argv ?? [] }).show;
13640
+ }
13641
+ const KEEP_ALIVE_VALUES = ["1", "true", "yes", "on"];
13642
+ function resolveLauncherWatchPlan(input) {
13643
+ const env = input.env ?? {};
13644
+ const keepAlive = (env[LAUNCHER_KEEP_ALIVE_ENV] ?? "").trim().toLowerCase();
13645
+ if (KEEP_ALIVE_VALUES.includes(keepAlive)) {
13646
+ return {
13647
+ watch: false,
13648
+ launcherPid: null,
13649
+ reason: `${LAUNCHER_KEEP_ALIVE_ENV}=${keepAlive} was set, so this run outlives its launcher`
13650
+ };
13651
+ }
13652
+ if (input.mode !== "headless") {
13653
+ return {
13654
+ watch: false,
13655
+ launcherPid: null,
13656
+ reason: `window mode ${input.mode} is not launcher-bound: a person can close it`
13657
+ };
13658
+ }
13659
+ if (!Number.isInteger(input.launcherPid) || input.launcherPid <= 1) {
13660
+ return {
13661
+ watch: false,
13662
+ launcherPid: null,
13663
+ reason: "headless run already detached (no launcher to outlive)"
13664
+ };
13665
+ }
13666
+ return {
13667
+ watch: true,
13668
+ launcherPid: input.launcherPid,
13669
+ reason: `headless run launched by pid ${input.launcherPid}; it quits when that process goes`
13670
+ };
13671
+ }
13672
+ function describeWindowLaunch(plan, platform = process.platform) {
13673
+ const behaviour = plan.show === "never" ? "window created and never shown" : plan.show === "inactive" ? "window shown without activating the app" : "window shown and focused";
13674
+ const assumption = plan.assumed ? ` (mode assumed: ${plan.assumed})` : "";
13675
+ const extras = [
13676
+ plan.hideDock && platform === "darwin" ? "no Dock tile" : null
13677
+ ].filter((part) => part !== null);
13678
+ const suffix = extras.length === 0 ? "" : `, ${extras.join(", ")}`;
13679
+ return `window mode ${plan.mode}: ${plan.width}x${plan.height}, ${behaviour}, page throttling ${plan.backgroundThrottling ? "on" : "off"}${suffix}${assumption}`;
13680
+ }
13681
+ const MODE_OF_SHOW = {
13682
+ focus: "normal",
13683
+ inactive: "inactive",
13684
+ never: "headless"
13685
+ };
13686
+ function requesterFields(context) {
13687
+ const who = context.requester;
13688
+ return [
13689
+ who?.pid === void 0 ? null : `pid=${who.pid}`,
13690
+ who?.cwd === void 0 ? null : `cwd=${who.cwd}`
13691
+ ].filter((field) => field !== null);
13692
+ }
13693
+ function raiseLine(context, requested, applied) {
13694
+ return [
13695
+ `trigger=${context.trigger}`,
13696
+ `mode=${MODE_OF_SHOW[requested]}`,
13697
+ `requested=${requested}`,
13698
+ ...requesterFields(context),
13699
+ `applied=${applied.join("+")}`
13700
+ ].join(" ");
13701
+ }
13702
+ function presentWindow(window, show, context) {
13442
13703
  if (show === "never") return;
13443
- if (window.isMinimized()) window.restore();
13444
- if (show === "inactive") window.showInactive();
13445
- else window.show();
13704
+ const applied = [];
13705
+ if (window.isMinimized()) {
13706
+ window.restore();
13707
+ applied.push("restore");
13708
+ }
13709
+ if (show === "inactive") {
13710
+ window.showInactive();
13711
+ applied.push("showInactive");
13712
+ } else {
13713
+ window.show();
13714
+ applied.push("show");
13715
+ }
13716
+ context.report?.(raiseLine(context, show, applied));
13717
+ }
13718
+ function canCreateWindowFor(show) {
13719
+ return show !== "never";
13720
+ }
13721
+ const OPERATOR_SHOW = "focus";
13722
+ function reportParked(session, context) {
13723
+ reportParkState([session], "parked", context);
13724
+ }
13725
+ function reportParkState(sessions, applied, context) {
13726
+ context.report?.(
13727
+ [
13728
+ `trigger=${context.trigger}`,
13729
+ `mode=${MODE_OF_SHOW.never}`,
13730
+ "requested=never",
13731
+ `parked=${sessions.join(",")}`,
13732
+ ...requesterFields(context),
13733
+ `applied=${applied}`
13734
+ ].join(" ")
13735
+ );
13736
+ }
13737
+ function reportParkedDelivered(session, context) {
13738
+ reportParkState([session], "delivered", context);
13739
+ }
13740
+ function reportParkedLeftWaiting(sessions, context) {
13741
+ reportParkState(sessions, "left+waiting", context);
13742
+ }
13743
+ function reportParkedEvicted(session, context) {
13744
+ reportParkState([session], "evicted", context);
13446
13745
  }
13447
- function raiseWindow(window, show) {
13746
+ function reportParksAtQuit(sessions, report) {
13747
+ report(
13748
+ [
13749
+ "trigger=app-quit",
13750
+ `mode=${MODE_OF_SHOW.never}`,
13751
+ "requested=never",
13752
+ `parked=${sessions.join(",")}`,
13753
+ "applied=dropped+quit"
13754
+ ].join(" ")
13755
+ );
13756
+ }
13757
+ function raiseWindow(window, show, context) {
13448
13758
  if (show === "never") return;
13449
- if (window.isMinimized()) window.restore();
13759
+ const applied = [];
13450
13760
  if (show === "inactive") {
13761
+ if (window.isMinimized()) {
13762
+ context.report?.(raiseLine(context, show, ["skipped", "minimised"]));
13763
+ return;
13764
+ }
13451
13765
  window.showInactive();
13766
+ applied.push("showInactive");
13767
+ } else {
13768
+ if (window.isMinimized()) {
13769
+ window.restore();
13770
+ applied.push("restore");
13771
+ }
13772
+ window.show();
13773
+ window.focus();
13774
+ applied.push("show", "focus");
13775
+ }
13776
+ context.report?.(raiseLine(context, show, applied));
13777
+ }
13778
+ function readSecondLaunchRequest(input) {
13779
+ const commandLine = input.commandLine ?? [];
13780
+ const intent = readWindowIntent(input.additionalData);
13781
+ const requester = {};
13782
+ if (intent?.pid !== void 0) requester.pid = intent.pid;
13783
+ const cwd = intent?.cwd ?? input.workingDirectory;
13784
+ if (cwd !== void 0 && cwd !== "") requester.cwd = cwd;
13785
+ return {
13786
+ session: readOpenSessionArgv(commandLine),
13787
+ show: resolveSecondLaunchShow({
13788
+ argv: commandLine,
13789
+ additionalData: input.additionalData
13790
+ }),
13791
+ requester: Object.keys(requester).length === 0 ? null : requester
13792
+ };
13793
+ }
13794
+ function applySecondLaunch(request, target2) {
13795
+ if (request.session !== null) {
13796
+ if (target2.openConversation) {
13797
+ target2.openConversation(request.session, request);
13798
+ } else {
13799
+ target2.queue(request.session, request);
13800
+ }
13801
+ return;
13802
+ }
13803
+ if (!target2.window) {
13804
+ if (canCreateWindowFor(request.show)) target2.openWindow?.(request);
13452
13805
  return;
13453
13806
  }
13454
- window.show();
13455
- window.focus();
13807
+ raiseWindow(target2.window, request.show, {
13808
+ trigger: "second-instance",
13809
+ requester: request.requester ?? void 0,
13810
+ report: target2.report
13811
+ });
13456
13812
  }
13457
13813
  const NOTIFY_TTL_MS = 10 * 60 * 1e3;
13458
13814
  const MAX_DEDUPE_KEYS = 2048;
@@ -13486,11 +13842,12 @@ const SILENT_HOST = {
13486
13842
  };
13487
13843
  const RENDERER_REPORT_TTL_MS = 45e3;
13488
13844
  class DesktopNotifier {
13489
- constructor(window, request, windowRaise = "focus", host = SILENT_HOST) {
13845
+ constructor(window, request, windowRaise = "focus", host = SILENT_HOST, raiseReport) {
13490
13846
  this.window = window;
13491
13847
  this.request = request;
13492
13848
  this.windowRaise = windowRaise;
13493
13849
  this.host = host;
13850
+ this.raiseReport = raiseReport;
13494
13851
  }
13495
13852
  delivered = /* @__PURE__ */ new Map();
13496
13853
  /** Last known frontend epoch per session, to key dedupe on owner epoch. */
@@ -14101,7 +14458,10 @@ class DesktopNotifier {
14101
14458
  return;
14102
14459
  }
14103
14460
  target2.webContents.send("desktop-open-conversation", { sessionId: sessionId2 });
14104
- raiseWindow(target2, this.windowRaise);
14461
+ raiseWindow(target2, this.windowRaise, {
14462
+ trigger: "banner-click",
14463
+ report: this.raiseReport
14464
+ });
14105
14465
  });
14106
14466
  notification.show();
14107
14467
  }
@@ -17144,176 +17504,6 @@ class ViewerRecordPublisher {
17144
17504
  }
17145
17505
  }
17146
17506
  }
17147
- const WINDOW_MODES = ["normal", "headless", "inactive"];
17148
- const WINDOW_MODE_ENV = "LOCAL_OPERATOR_UI_WINDOW_MODE";
17149
- const WINDOW_SIZE_ENV = "LOCAL_OPERATOR_UI_WINDOW_SIZE";
17150
- const WINDOW_MODE_FLAG = "--window-mode";
17151
- const WINDOW_SIZE_FLAG = "--window-size";
17152
- const AGENT_LAUNCH_FLAGS = [
17153
- "--user-data-dir",
17154
- "--remote-debugging-port"
17155
- ];
17156
- const LAUNCHER_KEEP_ALIVE_ENV = "LOCAL_OPERATOR_UI_HEADLESS_KEEP_ALIVE";
17157
- const LAUNCHER_POLL_INTERVAL_MS = 2e3;
17158
- const LAUNCHER_EXIT_DEADLINE_MS = 1e4;
17159
- const WINDOW_MIN_WIDTH = 800;
17160
- const WINDOW_MIN_HEIGHT = 600;
17161
- const WINDOW_MAX_EDGE = 16384;
17162
- const DEFAULT_WINDOW_WIDTH = 1380;
17163
- const DEFAULT_WINDOW_HEIGHT = 900;
17164
- const WINDOW_BEHAVIOUR = {
17165
- normal: {
17166
- show: "focus",
17167
- focusable: true,
17168
- backgroundThrottling: true,
17169
- hideDock: false
17170
- },
17171
- inactive: {
17172
- show: "inactive",
17173
- focusable: true,
17174
- backgroundThrottling: false,
17175
- // Visible on purpose, so it keeps its tile: a run somebody wants to
17176
- // watch has to be findable in the Dock.
17177
- hideDock: false
17178
- },
17179
- headless: {
17180
- show: "never",
17181
- focusable: false,
17182
- backgroundThrottling: false,
17183
- hideDock: true
17184
- }
17185
- };
17186
- function readFlag(argv, name) {
17187
- for (let index = 0; index < argv.length; index += 1) {
17188
- const argument = argv[index];
17189
- if (argument === name) {
17190
- const next = argv[index + 1];
17191
- return { found: true, value: next?.startsWith("--") ? void 0 : next };
17192
- }
17193
- if (argument.startsWith(`${name}=`)) {
17194
- return { found: true, value: argument.slice(name.length + 1) };
17195
- }
17196
- }
17197
- return { found: false, value: void 0 };
17198
- }
17199
- function parseWindowMode(value) {
17200
- if (value === void 0) return null;
17201
- const normalized = value.trim().toLowerCase();
17202
- return WINDOW_MODES.includes(normalized) ? normalized : null;
17203
- }
17204
- const WINDOW_SIZE_PATTERN = /^(\d+)\s*[x×]\s*(\d+)$/i;
17205
- function parseWindowSize(value) {
17206
- if (value === void 0) return null;
17207
- const match = value.trim().match(WINDOW_SIZE_PATTERN);
17208
- if (!match) return null;
17209
- const width = Number(match[1]);
17210
- const height = Number(match[2]);
17211
- if (width <= 0 || height <= 0) return null;
17212
- return { width, height };
17213
- }
17214
- function resolveWindowLaunchPlan(input = {}) {
17215
- const env = input.env ?? {};
17216
- const argv = input.argv ?? [];
17217
- const problems = [];
17218
- const modeFlag = readFlag(argv, WINDOW_MODE_FLAG);
17219
- const modeValue = modeFlag.found ? modeFlag.value : env[WINDOW_MODE_ENV];
17220
- const modeRaw = typeof modeValue === "string" && modeValue.trim() === "" ? void 0 : modeValue;
17221
- const parsedMode = parseWindowMode(modeRaw);
17222
- const unnamed = !modeFlag.found && modeRaw === void 0;
17223
- const agentFlag = unnamed ? AGENT_LAUNCH_FLAGS.find((flag) => readFlag(argv, flag).found) : void 0;
17224
- const platform = input.platform ?? process.platform;
17225
- const driven = unnamed && agentFlag === void 0 && input.packaged === false && platform !== "win32" && input.stdinIsTTY !== true && input.stdoutIsTTY !== true;
17226
- const assumed = agentFlag ? `${agentFlag} marks an agent-driven launch, and no window mode was named` : driven ? "the launch is not a packaged app and has no terminal on either stream, so it is a driven run rather than the operator using the app" : null;
17227
- if (modeFlag.found && modeFlag.value === void 0) {
17228
- problems.push(
17229
- `${WINDOW_MODE_FLAG} needs a value: ${WINDOW_MODES.join("|")}`
17230
- );
17231
- } else if (modeValue !== void 0 && modeRaw === void 0) {
17232
- problems.push(
17233
- `${modeFlag.found ? WINDOW_MODE_FLAG : WINDOW_MODE_ENV} names no mode (empty value)${assumed ? "" : "; using normal"}`
17234
- );
17235
- } else if (parsedMode === null && modeRaw !== void 0) {
17236
- problems.push(
17237
- `${modeFlag.found ? WINDOW_MODE_FLAG : WINDOW_MODE_ENV}="${modeRaw}" is not one of ${WINDOW_MODES.join("|")}; using normal`
17238
- );
17239
- }
17240
- const mode = parsedMode ?? (assumed ? "headless" : "normal");
17241
- const sizeFlag = readFlag(argv, WINDOW_SIZE_FLAG);
17242
- const sizeRaw = sizeFlag.found ? sizeFlag.value : env[WINDOW_SIZE_ENV];
17243
- const parsedSize = parseWindowSize(sizeRaw);
17244
- if (sizeFlag.found && sizeFlag.value === void 0) {
17245
- problems.push(`${WINDOW_SIZE_FLAG} needs a value: <width>x<height>`);
17246
- } else if (parsedSize === null && sizeRaw !== void 0) {
17247
- problems.push(
17248
- `${sizeFlag.found ? WINDOW_SIZE_FLAG : WINDOW_SIZE_ENV}="${sizeRaw}" is not <width>x<height>; using ${DEFAULT_WINDOW_WIDTH}x${DEFAULT_WINDOW_HEIGHT}`
17249
- );
17250
- }
17251
- const requested = parsedSize ?? {
17252
- width: DEFAULT_WINDOW_WIDTH,
17253
- height: DEFAULT_WINDOW_HEIGHT
17254
- };
17255
- const width = Math.min(
17256
- Math.max(requested.width, WINDOW_MIN_WIDTH),
17257
- WINDOW_MAX_EDGE
17258
- );
17259
- const height = Math.min(
17260
- Math.max(requested.height, WINDOW_MIN_HEIGHT),
17261
- WINDOW_MAX_EDGE
17262
- );
17263
- if (width !== requested.width || height !== requested.height) {
17264
- problems.push(
17265
- `window size ${requested.width}x${requested.height} clamped to ${width}x${height} (floor ${WINDOW_MIN_WIDTH}x${WINDOW_MIN_HEIGHT}, ceiling ${WINDOW_MAX_EDGE})`
17266
- );
17267
- }
17268
- return {
17269
- mode,
17270
- assumed,
17271
- ...WINDOW_BEHAVIOUR[mode],
17272
- width,
17273
- height,
17274
- problems
17275
- };
17276
- }
17277
- const KEEP_ALIVE_VALUES = ["1", "true", "yes", "on"];
17278
- function resolveLauncherWatchPlan(input) {
17279
- const env = input.env ?? {};
17280
- const keepAlive = (env[LAUNCHER_KEEP_ALIVE_ENV] ?? "").trim().toLowerCase();
17281
- if (KEEP_ALIVE_VALUES.includes(keepAlive)) {
17282
- return {
17283
- watch: false,
17284
- launcherPid: null,
17285
- reason: `${LAUNCHER_KEEP_ALIVE_ENV}=${keepAlive} was set, so this run outlives its launcher`
17286
- };
17287
- }
17288
- if (input.mode !== "headless") {
17289
- return {
17290
- watch: false,
17291
- launcherPid: null,
17292
- reason: `window mode ${input.mode} is not launcher-bound: a person can close it`
17293
- };
17294
- }
17295
- if (!Number.isInteger(input.launcherPid) || input.launcherPid <= 1) {
17296
- return {
17297
- watch: false,
17298
- launcherPid: null,
17299
- reason: "headless run already detached (no launcher to outlive)"
17300
- };
17301
- }
17302
- return {
17303
- watch: true,
17304
- launcherPid: input.launcherPid,
17305
- reason: `headless run launched by pid ${input.launcherPid}; it quits when that process goes`
17306
- };
17307
- }
17308
- function describeWindowLaunch(plan, platform = process.platform) {
17309
- const behaviour = plan.show === "never" ? "window created and never shown" : plan.show === "inactive" ? "window shown without activating the app" : "window shown and focused";
17310
- const assumption = plan.assumed ? ` (mode assumed: ${plan.assumed})` : "";
17311
- const extras = [
17312
- plan.hideDock && platform === "darwin" ? "no Dock tile" : null
17313
- ].filter((part) => part !== null);
17314
- const suffix = extras.length === 0 ? "" : `, ${extras.join(", ")}`;
17315
- return `window mode ${plan.mode}: ${plan.width}x${plan.height}, ${behaviour}, page throttling ${plan.backgroundThrottling ? "on" : "off"}${suffix}${assumption}`;
17316
- }
17317
17507
  const BASE64_FILE_EXTENSIONS = ["csv", "tsv", "xls", "xlsx", "ods"];
17318
17508
  const resolveUserPath = (filePath, cwd) => {
17319
17509
  if (filePath === "~") return electron.app.getPath("home");
@@ -17349,9 +17539,6 @@ for (const problem of windowLaunch.problems) {
17349
17539
  logger.warn(`[window-mode] ${problem}`, LogFileType.BACKEND);
17350
17540
  console.log(`[window-mode] ${problem}`);
17351
17541
  }
17352
- if (windowLaunch.mode !== "normal") {
17353
- console.log(`[window-mode] ${describeWindowLaunch(windowLaunch)}`);
17354
- }
17355
17542
  let headlessExitDeadline = null;
17356
17543
  function armHeadlessExitDeadline(why) {
17357
17544
  if (headlessExitDeadline) return;
@@ -17531,7 +17718,11 @@ function createApplicationMenu() {
17531
17718
  const menu = electron.Menu.buildFromTemplate(template);
17532
17719
  electron.Menu.setApplicationMenu(menu);
17533
17720
  }
17534
- function createWindow(initialSession = null, openCatalogue = false) {
17721
+ const ownLaunchRequest = () => ({
17722
+ show: windowLaunch.show,
17723
+ trigger: "initial-present"
17724
+ });
17725
+ function createWindow(initialSession = null, openCatalogue = false, request = ownLaunchRequest()) {
17535
17726
  const mainWindow2 = new electron.BrowserWindow({
17536
17727
  width: windowLaunch.width,
17537
17728
  height: windowLaunch.height,
@@ -17606,15 +17797,23 @@ function createWindow(initialSession = null, openCatalogue = false) {
17606
17797
  ...rendererArgumentFlags(initialSession, openCatalogue)
17607
17798
  }
17608
17799
  });
17609
- if (initialSession) holdPresentUntilConversation(mainWindow2, initialSession);
17800
+ if (initialSession)
17801
+ holdPresentUntilConversation(mainWindow2, initialSession, request);
17610
17802
  mainWindow2.webContents.on("console-message", (_event, level, message) => {
17611
17803
  if (level < 2) return;
17612
17804
  const write = level >= 3 ? logger.error : logger.warn;
17613
17805
  write.call(logger, `[renderer] ${message}`, LogFileType.BACKEND);
17614
17806
  });
17807
+ let presentHandled = false;
17615
17808
  mainWindow2.on("ready-to-show", () => {
17809
+ if (presentHandled) return;
17810
+ presentHandled = true;
17616
17811
  if (!heldForConversation.has(mainWindow2.id)) {
17617
- presentWindow(mainWindow2, windowLaunch.show);
17812
+ presentWindow(mainWindow2, request.show, {
17813
+ trigger: request.trigger,
17814
+ requester: request.requester,
17815
+ report: reportRaise
17816
+ });
17618
17817
  }
17619
17818
  if (windowLaunch.mode === "normal") return;
17620
17819
  setTimeout(() => {
@@ -17706,6 +17905,20 @@ const reportBackendFailure = (message, fileType) => {
17706
17905
  }
17707
17906
  electron.dialog.showErrorBox("Backend Error", message);
17708
17907
  };
17908
+ const reportRaise = (line) => {
17909
+ logger.info(`[window-raise] ${line}`, LogFileType.BACKEND);
17910
+ };
17911
+ function describeForwardedLaunch(profile, session) {
17912
+ const named = session === null ? null : `the conversation it named (${session})`;
17913
+ const handed = named === null ? "it named no conversation" : `${named} rides with it`;
17914
+ const effect = windowLaunch.show === "never" ? named === null ? `${handed}, so nothing is waiting to be opened, and it will not raise a window in the meantime` : `${handed}: the app will open it once a window is open, and it will not raise a window in the meantime (up to ${PARKED_LAUNCH_LIMIT} conversations wait; an older one is dropped and logged)` : windowLaunch.show === "inactive" ? `${handed}; the app may order its window forward without activating it` : `${handed}; the app will raise its window`;
17915
+ return `[second-instance] this launch did not start a window of its own: the app already open is the instance answering (profile: ${profile}), and this launch's window mode (${windowLaunch.mode}) was handed to it — ${effect}. Quit that app to start a fresh instance. Exiting.`;
17916
+ }
17917
+ const secondInstanceRequest = (request) => ({
17918
+ show: request.show,
17919
+ trigger: "second-instance",
17920
+ requester: request.requester ?? void 0
17921
+ });
17709
17922
  const devDriverArming = resolveDevDriverArming({
17710
17923
  env: launchEnv,
17711
17924
  windowMode: windowLaunch.mode
@@ -17732,19 +17945,67 @@ function rendererArgumentFlags(initialSession, openCatalogue) {
17732
17945
  ];
17733
17946
  return flags.length > 0 ? { additionalArguments: flags } : {};
17734
17947
  }
17735
- let queuedSession = null;
17948
+ const parkedLaunches = [];
17949
+ const PARKED_LAUNCH_LIMIT = 16;
17950
+ function parkLaunch(session, request) {
17951
+ parkedLaunches.push({ session, request });
17952
+ reportParked(session, {
17953
+ trigger: request.trigger,
17954
+ requester: request.requester,
17955
+ report: reportRaise
17956
+ });
17957
+ if (parkedLaunches.length <= PARKED_LAUNCH_LIMIT) return;
17958
+ const evicted = parkedLaunches.shift();
17959
+ if (evicted) {
17960
+ reportParkedEvicted(evicted.session, {
17961
+ trigger: evicted.request.trigger,
17962
+ requester: evicted.request.requester,
17963
+ report: reportRaise
17964
+ });
17965
+ }
17966
+ }
17967
+ function claimParkedFor(window, deliver, painted) {
17968
+ const claimed = parkedLaunches.slice(0, parkedLaunches.length);
17969
+ if (claimed.length === 0) return 0;
17970
+ window.webContents.once("did-finish-load", () => {
17971
+ for (const queued of claimed) {
17972
+ const at = parkedLaunches.indexOf(queued);
17973
+ if (at === -1) continue;
17974
+ parkedLaunches.splice(at, 1);
17975
+ reportParkedDelivered(queued.session, {
17976
+ trigger: queued.request.trigger,
17977
+ requester: queued.request.requester,
17978
+ report: reportRaise
17979
+ });
17980
+ if (queued === painted) continue;
17981
+ deliver(queued.session, queued.request);
17982
+ }
17983
+ });
17984
+ window.once("closed", () => {
17985
+ for (const queued of claimed) {
17986
+ if (!parkedLaunches.includes(queued)) continue;
17987
+ reportParkedLeftWaiting([queued.session], {
17988
+ trigger: queued.request.trigger,
17989
+ requester: queued.request.requester,
17990
+ report: reportRaise
17991
+ });
17992
+ }
17993
+ });
17994
+ return claimed.length;
17995
+ }
17736
17996
  let viewerRecord = null;
17737
17997
  let viewerEndpoint = null;
17738
17998
  let openConversationInWindow = null;
17999
+ let openOwnWindow = null;
17739
18000
  let attachBrowserHostToWindow = null;
17740
18001
  const CONVERSATION_PAINT_FALLBACK_MS = 3e3;
17741
18002
  const heldForConversation = /* @__PURE__ */ new Map();
17742
- function holdPresentUntilConversation(window, session) {
18003
+ function holdPresentUntilConversation(window, session, request) {
17743
18004
  const timer = setTimeout(() => {
17744
18005
  releaseHeldWindow(window.id);
17745
18006
  }, CONVERSATION_PAINT_FALLBACK_MS);
17746
18007
  timer.unref?.();
17747
- heldForConversation.set(window.id, { session, timer });
18008
+ heldForConversation.set(window.id, { session, timer, request });
17748
18009
  }
17749
18010
  function releaseHeldWindow(windowId) {
17750
18011
  const held = heldForConversation.get(windowId);
@@ -17752,7 +18013,13 @@ function releaseHeldWindow(windowId) {
17752
18013
  clearTimeout(held.timer);
17753
18014
  heldForConversation.delete(windowId);
17754
18015
  const window = electron.BrowserWindow.fromId(windowId);
17755
- if (window && !window.isDestroyed()) presentWindow(window, windowLaunch.show);
18016
+ if (window && !window.isDestroyed()) {
18017
+ presentWindow(window, held.request.show, {
18018
+ trigger: held.request.trigger,
18019
+ requester: held.request.requester,
18020
+ report: reportRaise
18021
+ });
18022
+ }
17756
18023
  return true;
17757
18024
  }
17758
18025
  function releaseHeldWindowFor(session) {
@@ -17780,22 +18047,67 @@ const actualSizeFromEvent = () => {
17780
18047
  mainWindow.webContents.setZoomFactor(1);
17781
18048
  }
17782
18049
  };
17783
- const gotTheLock = electron.app.requestSingleInstanceLock();
18050
+ const gotTheLock = electron.app.requestSingleInstanceLock(
18051
+ windowIntentPayload(windowLaunch.mode, {
18052
+ // What the winner's log line names when it acts on this request (UX U2).
18053
+ // Declared by this process about itself, so it is diagnostic only: nothing
18054
+ // here is ever decided from it.
18055
+ pid: process.pid,
18056
+ cwd: process.cwd()
18057
+ })
18058
+ );
17784
18059
  if (!gotTheLock) {
17785
18060
  logger.warn("Another instance is already running. Quitting this instance.");
18061
+ console.log(
18062
+ describeForwardedLaunch(
18063
+ electron.app.getPath("userData"),
18064
+ readSecondLaunchRequest({ commandLine: process.argv }).session
18065
+ )
18066
+ );
17786
18067
  electron.app.quit();
17787
18068
  } else {
17788
- electron.app.on("second-instance", (_event, commandLine) => {
17789
- const requested = readOpenSessionArgv(commandLine);
17790
- if (requested) {
17791
- if (openConversationInWindow) openConversationInWindow(requested);
17792
- else queuedSession = requested;
17793
- return;
17794
- }
17795
- if (mainWindow) {
17796
- raiseWindow(mainWindow, windowLaunch.show);
18069
+ if (windowLaunch.mode !== "normal") {
18070
+ console.log(`[window-mode] ${describeWindowLaunch(windowLaunch)}`);
18071
+ }
18072
+ electron.app.on(
18073
+ "second-instance",
18074
+ (_event, commandLine, workingDirectory, additionalData) => {
18075
+ applySecondLaunch(
18076
+ readSecondLaunchRequest({
18077
+ commandLine,
18078
+ additionalData,
18079
+ workingDirectory
18080
+ }),
18081
+ {
18082
+ window: mainWindow,
18083
+ openConversation: openConversationInWindow ? (session, request) => openConversationInWindow?.(
18084
+ session,
18085
+ secondInstanceRequest(request)
18086
+ ) : null,
18087
+ /*
18088
+ * No window, and nothing to deliver to it: a request that may come forward
18089
+ * opens the app's own window, exactly as the app's own launch does. It used
18090
+ * to do nothing at all (`if (!target.window) return`), so the operator
18091
+ * launching the app again while it ran saw nothing appear — and a
18092
+ * conversation parked by a `headless` request was never opened by the
18093
+ * launch that should have opened it. The window this creates consumes
18094
+ * whatever is parked as its initial session.
18095
+ */
18096
+ openWindow: openOwnWindow ? (request) => openOwnWindow?.(secondInstanceRequest(request)) : null,
18097
+ // Parked, not dropped: a second instance can arrive while the first is
18098
+ // still starting, and a discarded id is a click that silently did
18099
+ // nothing — the defect this path exists to remove. The request is parked
18100
+ // WITH it, so the flush cannot re-derive it from argv that is gone, and
18101
+ // the queue holds EVERY parked request rather than the last one
18102
+ // (review round 2, MAJOR-1).
18103
+ queue: (session, request) => {
18104
+ parkLaunch(session, secondInstanceRequest(request));
18105
+ },
18106
+ report: reportRaise
18107
+ }
18108
+ );
17797
18109
  }
17798
- });
18110
+ );
17799
18111
  }
17800
18112
  electron.app.whenReady().then(async () => {
17801
18113
  utils.electronApp.setAppUserModelId("com.local-operator");
@@ -17857,7 +18169,14 @@ electron.app.whenReady().then(async () => {
17857
18169
  },
17858
18170
  // A banner click with no window recreates one and navigates it. The
17859
18171
  // queue lives in `openSessionInWindow` because window creation does.
17860
- reopen: (sessionId2) => openSessionInWindow(sessionId2),
18172
+ // A banner click's recreate path: the window it makes is presented as far
18173
+ // as THIS process's launch plan allows, because the click came from inside
18174
+ // this process. The trigger names the click rather than the delivery, so
18175
+ // the log tells the three conversation-carrying requests apart.
18176
+ reopen: (sessionId2) => openSessionInWindow(sessionId2, {
18177
+ show: windowLaunch.show,
18178
+ trigger: "banner-click"
18179
+ }),
17861
18180
  /*
17862
18181
  * The renderer's report that this conversation is on screen. Two
17863
18182
  * consumers, and neither is delivery:
@@ -17872,7 +18191,10 @@ electron.app.whenReady().then(async () => {
17872
18191
  viewerRecord?.noteSession(sessionId2);
17873
18192
  releaseHeldWindowFor(sessionId2);
17874
18193
  }
17875
- }
18194
+ },
18195
+ // A clicked banner comes forward through this same raise policy, and its
18196
+ // line is what tells that trigger apart from the other four.
18197
+ reportRaise
17876
18198
  );
17877
18199
  backendService.onBackendReady(() => {
17878
18200
  void desktopNotifier.refreshNotificationContract();
@@ -17914,13 +18236,19 @@ electron.app.whenReady().then(async () => {
17914
18236
  const endpoint2 = new ViewerEndpoint(
17915
18237
  {
17916
18238
  resumeSession: async (sessionId2) => {
17917
- openSessionInWindow(sessionId2);
18239
+ openSessionInWindow(sessionId2, {
18240
+ show: windowLaunch.show,
18241
+ trigger: "viewer-resume"
18242
+ });
17918
18243
  return `showing ${sessionId2}`;
17919
18244
  },
17920
18245
  focusWindow: async () => {
17921
18246
  const window = mainWindow;
17922
18247
  if (window && !window.isDestroyed()) {
17923
- raiseWindow(window, windowLaunch.show);
18248
+ raiseWindow(window, windowLaunch.show, {
18249
+ trigger: "viewer-focus",
18250
+ report: reportRaise
18251
+ });
17924
18252
  return "raised the window";
17925
18253
  }
17926
18254
  setupMainWindowWithUpdateService();
@@ -18307,9 +18635,19 @@ electron.app.whenReady().then(async () => {
18307
18635
  }
18308
18636
  createApplicationMenu();
18309
18637
  let updateService = null;
18310
- function setupMainWindowWithUpdateService(initialSession = null, openCatalogue = false) {
18311
- mainWindow = createWindow(initialSession, openCatalogue);
18638
+ function setupMainWindowWithUpdateService(initialSession = null, openCatalogue = false, request = ownLaunchRequest()) {
18639
+ const parked = initialSession === null && !openCatalogue ? parkedLaunches[0] : void 0;
18640
+ mainWindow = createWindow(
18641
+ parked?.session ?? initialSession,
18642
+ openCatalogue,
18643
+ request
18644
+ );
18312
18645
  const windowId = mainWindow.webContents.id;
18646
+ claimParkedFor(
18647
+ mainWindow,
18648
+ (session, request2) => openSessionInWindow(session, request2),
18649
+ parked
18650
+ );
18313
18651
  if (mainWindow) {
18314
18652
  mainWindow.webContents.on("before-input-event", (event, input) => {
18315
18653
  const isCmdOrCtrl = input.control || input.meta;
@@ -18399,28 +18737,35 @@ electron.app.whenReady().then(async () => {
18399
18737
  );
18400
18738
  }
18401
18739
  }
18402
- function openSessionInWindow(sessionId2) {
18740
+ function openSessionInWindow(sessionId2, request) {
18403
18741
  const window = mainWindow;
18404
18742
  if (window && !window.isDestroyed()) {
18405
18743
  window.webContents.send("desktop-open-conversation", { sessionId: sessionId2 });
18406
- raiseWindow(window, windowLaunch.show);
18744
+ raiseWindow(window, request.show, {
18745
+ trigger: request.trigger,
18746
+ requester: request.requester,
18747
+ report: reportRaise
18748
+ });
18407
18749
  return;
18408
18750
  }
18409
- setupMainWindowWithUpdateService(sessionId2, sessionId2 === null);
18751
+ if (!canCreateWindowFor(request.show)) {
18752
+ if (sessionId2 !== null) parkLaunch(sessionId2, request);
18753
+ return;
18754
+ }
18755
+ setupMainWindowWithUpdateService(sessionId2, sessionId2 === null, request);
18410
18756
  }
18411
18757
  openConversationInWindow = openSessionInWindow;
18758
+ openOwnWindow = (request) => setupMainWindowWithUpdateService(null, false, request);
18412
18759
  attachBrowserHostToWindow = (window) => {
18413
18760
  void startBrowserHostForWindow(window);
18414
18761
  };
18415
18762
  setupMainWindowWithUpdateService(launchSession, launchCatalogue);
18416
- if (queuedSession) {
18417
- const queued = queuedSession;
18418
- queuedSession = null;
18419
- openSessionInWindow(queued);
18420
- }
18421
18763
  electron.app.on("activate", () => {
18422
18764
  if (electron.BrowserWindow.getAllWindows().length === 0) {
18423
- setupMainWindowWithUpdateService();
18765
+ setupMainWindowWithUpdateService(null, false, {
18766
+ show: OPERATOR_SHOW,
18767
+ trigger: "initial-present"
18768
+ });
18424
18769
  }
18425
18770
  });
18426
18771
  }).catch((error) => {
@@ -18501,6 +18846,12 @@ const holdQuitForSessionCookieSnapshot = createSessionCookieQuitHold({
18501
18846
  electron.app.on("before-quit", async (event) => {
18502
18847
  if (await holdQuitForSessionCookieSnapshot(event)) return;
18503
18848
  logger.info("App is about to quit", LogFileType.BACKEND);
18849
+ if (parkedLaunches.length > 0) {
18850
+ reportParksAtQuit(
18851
+ parkedLaunches.map((parked) => parked.session),
18852
+ reportRaise
18853
+ );
18854
+ }
18504
18855
  if (windowLaunch.mode === "headless") {
18505
18856
  launcherWatch?.stop();
18506
18857
  armHeadlessExitDeadline("the app is quitting");