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.
- package/README.md +27 -0
- package/out/main/index.js +567 -216
- package/out/renderer/assets/{_basePickBy-CUI9MZ4f.js → _basePickBy-CU6cbfV-.js} +1 -1
- package/out/renderer/assets/{_baseUniq-DsrAy52N.js → _baseUniq-3eW3-sWy.js} +1 -1
- package/out/renderer/assets/{agent-details-page-Br7O8DtR.js → agent-details-page-B9tJtnsB.js} +1 -1
- package/out/renderer/assets/{agent-hub-page-Cm0yzEub.js → agent-hub-page-DzfzgwPB.js} +1 -1
- package/out/renderer/assets/{agents-page-BBfNcLHk.js → agents-page-BvOGxAAb.js} +2 -2
- package/out/renderer/assets/{arc-CUR3ZFhl.js → arc-2_87zcwb.js} +1 -1
- package/out/renderer/assets/{architectureDiagram-IEHRJDOE-DOji4S3Q.js → architectureDiagram-IEHRJDOE-Dfs7RsUV.js} +1 -1
- package/out/renderer/assets/{arrow-left-DnuA26Fd.js → arrow-left-CcktVilP.js} +1 -1
- package/out/renderer/assets/{blockDiagram-JOT3LUYC-C9r-1qgy.js → blockDiagram-JOT3LUYC-DNwa0Hfv.js} +1 -1
- package/out/renderer/assets/{browser-page-Cq_rvMea.js → browser-page-CBufKj--.js} +1 -1
- package/out/renderer/assets/{c4Diagram-VJAJSXHY-CgVXRHMX.js → c4Diagram-VJAJSXHY-KeZkxF7Y.js} +1 -1
- package/out/renderer/assets/channel-D3Qk6DSD.js +1 -0
- package/out/renderer/assets/{chunk-4BMEZGHF-BCCDYlHx.js → chunk-4BMEZGHF-BTCRyTKZ.js} +1 -1
- package/out/renderer/assets/{chunk-A2AXSNBT-DQv_q1F4.js → chunk-A2AXSNBT-DLMWYufm.js} +1 -1
- package/out/renderer/assets/{chunk-AEK57VVT-uiKrJ6rs.js → chunk-AEK57VVT-DIOpHfUC.js} +1 -1
- package/out/renderer/assets/{chunk-D6G4REZN-DGNuSfnq.js → chunk-D6G4REZN-3kvxiifv.js} +1 -1
- package/out/renderer/assets/{chunk-RZ5BOZE2-CZOZ0qYQ.js → chunk-RZ5BOZE2-DNWczlot.js} +1 -1
- package/out/renderer/assets/{chunk-XZIHB7SX-LBl420Pq.js → chunk-XZIHB7SX-x5aN2jja.js} +1 -1
- package/out/renderer/assets/classDiagram-GIVACNV2-SA9scKTw.js +1 -0
- package/out/renderer/assets/classDiagram-v2-COTLJTTW-SA9scKTw.js +1 -0
- package/out/renderer/assets/clone-Bl4GsLEn.js +1 -0
- package/out/renderer/assets/{compact-pagination-CRUt_cmC.js → compact-pagination-DKAy-IR2.js} +1 -1
- package/out/renderer/assets/{dagre-OKDRZEBW-BvGSIi2I.js → dagre-OKDRZEBW-CDUKiMzY.js} +1 -1
- package/out/renderer/assets/{diagram-SSKATNLV-47O2muUk.js → diagram-SSKATNLV-B2kH_yx8.js} +1 -1
- package/out/renderer/assets/{diagram-VNBRO52H-CG4_K9jG.js → diagram-VNBRO52H-jfKWU-Lm.js} +1 -1
- package/out/renderer/assets/{erDiagram-Q7BY3M3F-BKPT4Ger.js → erDiagram-Q7BY3M3F-7XQUHMG0.js} +1 -1
- package/out/renderer/assets/{flowDiagram-4HSFHLVR-C6OqF7rj.js → flowDiagram-4HSFHLVR-TzsaCK4n.js} +1 -1
- package/out/renderer/assets/{ganttDiagram-APWFNJXF-BsAxR8C1.js → ganttDiagram-APWFNJXF-yehmMRqe.js} +1 -1
- package/out/renderer/assets/{gitGraphDiagram-7IBYFJ6S-Jy7DxrgG.js → gitGraphDiagram-7IBYFJ6S-BjJi9W8H.js} +1 -1
- package/out/renderer/assets/{graph-BZYPzyg2.js → graph-qAJoLSKS.js} +1 -1
- package/out/renderer/assets/icon-DU3qS4xX.css +1 -0
- package/out/renderer/assets/{index-RZD44-S3.js → index-B59GZTl_.js} +1 -1
- package/out/renderer/assets/{index-DVeeuYz1.js → index-CRN0i7I1.js} +1 -1
- package/out/renderer/assets/{index-CFct9cqp.js → index-Du_04xLY.js} +356 -356
- package/out/renderer/assets/{infoDiagram-PH2N3AL5-dmY9mvLF.js → infoDiagram-PH2N3AL5--ktj13x4.js} +1 -1
- package/out/renderer/assets/{installer-BUekjhed.js → installer-B_gSn64f.js} +1 -1
- package/out/renderer/assets/{journeyDiagram-U35MCT3I-XmmyNpv5.js → journeyDiagram-U35MCT3I-BKVaP-gO.js} +1 -1
- package/out/renderer/assets/{kanban-definition-NDS4AKOZ-BVCPlSpm.js → kanban-definition-NDS4AKOZ-CeSOSCcU.js} +1 -1
- package/out/renderer/assets/{layout-CxeJgGG0.js → layout-D2Cfz3aw.js} +1 -1
- package/out/renderer/assets/{legacy-agents-page-DNgwgCyB.js → legacy-agents-page-5_OwD2vD.js} +1 -1
- package/out/renderer/assets/{mermaid.core-CXWbTsAT.js → mermaid.core-DsqDVkGt.js} +5 -5
- package/out/renderer/assets/{mindmap-definition-ALO5MXBD-B5EFMpzK.js → mindmap-definition-ALO5MXBD-cXlbqLJn.js} +1 -1
- package/out/renderer/assets/{page-header-M5DAJreE.js → page-header-ehyv1ujj.js} +1 -1
- package/out/renderer/assets/{parseISO-CDh833Jr.js → parseISO-CHtHZJU3.js} +1 -1
- package/out/renderer/assets/{pieDiagram-IB7DONF6-Ccb705k-.js → pieDiagram-IB7DONF6-BnYWJ3Cc.js} +1 -1
- package/out/renderer/assets/{quadrantDiagram-7GDLP6J5-KRot01vw.js → quadrantDiagram-7GDLP6J5-DzGWwKDC.js} +1 -1
- package/out/renderer/assets/{radar-MK3ICKWK-w1jSyppR.js → radar-MK3ICKWK-Cz8qP9DC.js} +1 -1
- package/out/renderer/assets/{radient-auth-buttons-D6VoYc_3.js → radient-auth-buttons-mhEV8xEj.js} +1 -1
- package/out/renderer/assets/{requirementDiagram-KVF5MWMF-D0OxQxeF.js → requirementDiagram-KVF5MWMF-Cx-mDTR6.js} +1 -1
- package/out/renderer/assets/{sankeyDiagram-QLVOVGJD-Z4AIMbdQ.js → sankeyDiagram-QLVOVGJD-Bc1DOPhd.js} +1 -1
- package/out/renderer/assets/{schedules-page-CA-Rg3H-.js → schedules-page-DWV64xxK.js} +1 -1
- package/out/renderer/assets/{sequenceDiagram-X6HHIX6F-DSrAwJWQ.js → sequenceDiagram-X6HHIX6F-CIN-8sm7.js} +1 -1
- package/out/renderer/assets/{settings-page-B8PPcN6r.js → settings-page-vtL6TaYz.js} +1 -1
- package/out/renderer/assets/{shield-check-Do9ljoiZ.js → shield-check-Biv7u3YI.js} +1 -1
- package/out/renderer/assets/{stateDiagram-DGXRK772-BPeN9LWy.js → stateDiagram-DGXRK772-DVmGEm-f.js} +1 -1
- package/out/renderer/assets/stateDiagram-v2-YXO3MK2T-Cr8PVgPH.js +1 -0
- package/out/renderer/assets/{timeline-definition-BDJGKUSR-CcUnQn74.js → timeline-definition-BDJGKUSR-B1LptBCI.js} +1 -1
- package/out/renderer/assets/{use-agent-like-mutation-BXm60QBg.js → use-agent-like-mutation-HFVpi3nG.js} +1 -1
- package/out/renderer/assets/{xychartDiagram-VJFVF3MP-DOHYtSG5.js → xychartDiagram-VJFVF3MP-CB_p7m3M.js} +1 -1
- package/out/renderer/index.html +3 -3
- package/out/renderer/installer.html +4 -4
- package/package.json +4 -3
- package/out/renderer/assets/channel-BWq61nTi.js +0 -1
- package/out/renderer/assets/classDiagram-GIVACNV2-DVldbDH9.js +0 -1
- package/out/renderer/assets/classDiagram-v2-COTLJTTW-DVldbDH9.js +0 -1
- package/out/renderer/assets/clone-BzYaT6pf.js +0 -1
- package/out/renderer/assets/icon-TFBsh9Po.css +0 -1
- package/out/renderer/assets/stateDiagram-v2-YXO3MK2T-B8kJy16V.js +0 -1
- /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
|
-
|
|
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
|
-
|
|
13444
|
-
if (
|
|
13445
|
-
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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)
|
|
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,
|
|
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
|
-
|
|
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())
|
|
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
|
-
|
|
17789
|
-
|
|
17790
|
-
|
|
17791
|
-
|
|
17792
|
-
|
|
17793
|
-
|
|
17794
|
-
|
|
17795
|
-
|
|
17796
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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,
|
|
18744
|
+
raiseWindow(window, request.show, {
|
|
18745
|
+
trigger: request.trigger,
|
|
18746
|
+
requester: request.requester,
|
|
18747
|
+
report: reportRaise
|
|
18748
|
+
});
|
|
18407
18749
|
return;
|
|
18408
18750
|
}
|
|
18409
|
-
|
|
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");
|