local-operator-ui 0.34.11 → 0.34.13
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/out/main/index.js +400 -6
- package/out/preload/index.js +20 -0
- package/out/renderer/assets/{_basePickBy-0FuZakDO.js → _basePickBy-CuMaQXnE.js} +1 -1
- package/out/renderer/assets/{_baseUniq-DF-R-zk_.js → _baseUniq-DKeCGkCd.js} +1 -1
- package/out/renderer/assets/{agent-details-page-Cpmz3DTx.js → agent-details-page-DNymI5K7.js} +1 -1
- package/out/renderer/assets/{agent-hub-page-BjdZHs7N.js → agent-hub-page-Ckv_jCjf.js} +1 -1
- package/out/renderer/assets/{agents-page-CFI7y-mh.js → agents-page-CzWn74Vs.js} +3 -3
- package/out/renderer/assets/{arc-hMevLt8i.js → arc-CnvhNTGl.js} +1 -1
- package/out/renderer/assets/{architectureDiagram-IEHRJDOE-B5fNwTBg.js → architectureDiagram-IEHRJDOE-D87FdX-H.js} +1 -1
- package/out/renderer/assets/{blockDiagram-JOT3LUYC-C0eifn71.js → blockDiagram-JOT3LUYC-pWG0SYM0.js} +1 -1
- package/out/renderer/assets/{browser-page-DOvqhWYH.js → browser-page-C90b-h7d.js} +1 -1
- package/out/renderer/assets/{browser-webauthn-prompt-BInNSrH7.js → browser-webauthn-prompt-D9pJNnAE.js} +1 -1
- package/out/renderer/assets/{c4Diagram-VJAJSXHY-B7AqGR0S.js → c4Diagram-VJAJSXHY-n9oZXPrb.js} +1 -1
- package/out/renderer/assets/channel-D7v5czrG.js +1 -0
- package/out/renderer/assets/{chunk-4BMEZGHF-DRgWg1wB.js → chunk-4BMEZGHF-DUVNV3px.js} +1 -1
- package/out/renderer/assets/{chunk-A2AXSNBT-QkYdfOR_.js → chunk-A2AXSNBT-D69ozLLm.js} +1 -1
- package/out/renderer/assets/{chunk-AEK57VVT-CCQ--tm8.js → chunk-AEK57VVT-DojzsKw7.js} +1 -1
- package/out/renderer/assets/{chunk-D6G4REZN-Czu2dN7h.js → chunk-D6G4REZN-CXPThrUO.js} +1 -1
- package/out/renderer/assets/{chunk-RZ5BOZE2-DYG4fw8w.js → chunk-RZ5BOZE2-Tpm4iz0-.js} +1 -1
- package/out/renderer/assets/{chunk-XZIHB7SX-CrsWczXN.js → chunk-XZIHB7SX-CM94sy6v.js} +1 -1
- package/out/renderer/assets/classDiagram-GIVACNV2-D8dzeJBj.js +1 -0
- package/out/renderer/assets/classDiagram-v2-COTLJTTW-D8dzeJBj.js +1 -0
- package/out/renderer/assets/clone-BY0fK-il.js +1 -0
- package/out/renderer/assets/{conversation-input-sync-jHBqtt9M.js → conversation-input-sync-BvSb6a8F.js} +245 -245
- package/out/renderer/assets/{dagre-OKDRZEBW-V_2TdXms.js → dagre-OKDRZEBW-CNxZp61j.js} +1 -1
- package/out/renderer/assets/{diagram-SSKATNLV-DmHiewpC.js → diagram-SSKATNLV-aLMzXyK5.js} +1 -1
- package/out/renderer/assets/{diagram-VNBRO52H-BBaLkKUI.js → diagram-VNBRO52H-Bg2DhWCA.js} +1 -1
- package/out/renderer/assets/{erDiagram-Q7BY3M3F-gzrT0OQq.js → erDiagram-Q7BY3M3F-j_TMas8g.js} +1 -1
- package/out/renderer/assets/{flowDiagram-4HSFHLVR-WtEIG1SZ.js → flowDiagram-4HSFHLVR-DwfE01r0.js} +1 -1
- package/out/renderer/assets/{ganttDiagram-APWFNJXF-B1jWM42b.js → ganttDiagram-APWFNJXF-o_J5Q4qE.js} +1 -1
- package/out/renderer/assets/{gitGraphDiagram-7IBYFJ6S-_mbC-N8Q.js → gitGraphDiagram-7IBYFJ6S-8AYyb2q7.js} +1 -1
- package/out/renderer/assets/{graph--j_bjGDp.js → graph-KSlxkPtr.js} +1 -1
- package/out/renderer/assets/{index-SDz2HLAt.js → index-Bxh_DMkN.js} +1 -1
- package/out/renderer/assets/{index-78Pu8oX4.js → index-CBio7uVX.js} +1 -1
- package/out/renderer/assets/{index-BXFAcfHW.js → index-Wfo25fxR.js} +189 -189
- package/out/renderer/assets/{infoDiagram-PH2N3AL5-ZAxI5P-K.js → infoDiagram-PH2N3AL5-Dgx0WJ8T.js} +1 -1
- package/out/renderer/assets/{journeyDiagram-U35MCT3I-DZ4RyZqB.js → journeyDiagram-U35MCT3I-De_U2_fi.js} +1 -1
- package/out/renderer/assets/{kanban-definition-NDS4AKOZ-kW-9JUyI.js → kanban-definition-NDS4AKOZ-DUuYeORq.js} +1 -1
- package/out/renderer/assets/{layout-hBM56uFl.js → layout-Y7ReWUo5.js} +1 -1
- package/out/renderer/assets/{legacy-agents-page-lsbEk2J1.js → legacy-agents-page-B9mXqb4D.js} +1 -1
- package/out/renderer/assets/{mermaid.core-DUwwjyuA.js → mermaid.core-DNS50-NS.js} +5 -5
- package/out/renderer/assets/{mesh-page-R-moWtJ_.js → mesh-page-fWdbRlK8.js} +1 -1
- package/out/renderer/assets/{mindmap-definition-ALO5MXBD-CabAvvHv.js → mindmap-definition-ALO5MXBD-D3_S2COi.js} +1 -1
- package/out/renderer/assets/{mini-Bk-Elwan.js → mini-Co_TXCJ3.js} +1 -1
- package/out/renderer/assets/{org-surface-gate-BblILKma.js → org-surface-gate-ik1uMiLe.js} +1 -1
- package/out/renderer/assets/{pieDiagram-IB7DONF6-DfPuydQj.js → pieDiagram-IB7DONF6-C8yur6Ac.js} +1 -1
- package/out/renderer/assets/{projects-page-vD-RuTFH.js → projects-page-XYebxBb4.js} +1 -1
- package/out/renderer/assets/{quadrantDiagram-7GDLP6J5-miOaNOa0.js → quadrantDiagram-7GDLP6J5-B04MajkG.js} +1 -1
- package/out/renderer/assets/{radar-MK3ICKWK-6uN5Oz3u.js → radar-MK3ICKWK-CLCEq4mP.js} +1 -1
- package/out/renderer/assets/{radient-auth-buttons-COiSnA2S.js → radient-auth-buttons-D7TF55K4.js} +1 -1
- package/out/renderer/assets/{requirementDiagram-KVF5MWMF-ZcBG_T3L.js → requirementDiagram-KVF5MWMF-zeGik6Fv.js} +1 -1
- package/out/renderer/assets/{sankeyDiagram-QLVOVGJD-B0yXArdY.js → sankeyDiagram-QLVOVGJD-BZmVH-PV.js} +1 -1
- package/out/renderer/assets/{schedules-page-BNxF_SHU.js → schedules-page-tkQ09Qzt.js} +1 -1
- package/out/renderer/assets/{sequenceDiagram-X6HHIX6F-Cy6KDW6o.js → sequenceDiagram-X6HHIX6F-UUGTprRZ.js} +1 -1
- package/out/renderer/assets/{settings-page-478PcsAW.js → settings-page-kJ4v4696.js} +1 -1
- package/out/renderer/assets/{stateDiagram-DGXRK772-SpJc3S_A.js → stateDiagram-DGXRK772-DpuddJOQ.js} +1 -1
- package/out/renderer/assets/{stateDiagram-v2-YXO3MK2T-CBW1IyeW.js → stateDiagram-v2-YXO3MK2T-DBnjJOPU.js} +1 -1
- package/out/renderer/assets/{timeline-definition-BDJGKUSR-BSbru3ra.js → timeline-definition-BDJGKUSR-mHNW2fr3.js} +1 -1
- package/out/renderer/assets/{use-download-agent-mutation-CfvFFmrZ.js → use-download-agent-mutation-CgAZq5FK.js} +1 -1
- package/out/renderer/assets/{use-memberships-query-B__m4YCP.js → use-memberships-query-CVbNGH7n.js} +1 -1
- package/out/renderer/assets/{use-session-snapshot-CF9Y72Da.js → use-session-snapshot-D3DsMux6.js} +1 -1
- package/out/renderer/assets/{xychartDiagram-VJFVF3MP-6d774UsI.js → xychartDiagram-VJFVF3MP-D4S76BDC.js} +1 -1
- package/out/renderer/index.html +2 -2
- package/out/renderer/mini.html +3 -3
- package/package.json +2 -2
- package/out/renderer/assets/channel-JotcBz3R.js +0 -1
- package/out/renderer/assets/classDiagram-GIVACNV2-CksS_rYL.js +0 -1
- package/out/renderer/assets/classDiagram-v2-COTLJTTW-CksS_rYL.js +0 -1
- package/out/renderer/assets/clone-BzHauEn5.js +0 -1
package/out/main/index.js
CHANGED
|
@@ -2088,6 +2088,26 @@ const desktopRequestUnion = zod.z.discriminatedUnion("op", [
|
|
|
2088
2088
|
*/
|
|
2089
2089
|
keys: zod.z.array(zod.z.string().min(1).max(512)).max(256).optional(),
|
|
2090
2090
|
force: zod.z.boolean().optional()
|
|
2091
|
+
}).strict(),
|
|
2092
|
+
/*
|
|
2093
|
+
* THE PREVIEWS' SIGNED STATIC URLS (file-serving RFC § 4, Phase A adoption).
|
|
2094
|
+
* The renderer cannot read the core's serve record (no filesystem, no record
|
|
2095
|
+
* store), so the previews ask MAIN - which already holds the desktop bearer -
|
|
2096
|
+
* to mint a short-lived signed URL for one static route + path. The route is
|
|
2097
|
+
* public in Phase A; this op is what lets the previews keep working when a
|
|
2098
|
+
* later core phase gates it, and a core that predates the route answers 404,
|
|
2099
|
+
* which the previews read as their fallback signal, not an error.
|
|
2100
|
+
*
|
|
2101
|
+
* `route` is the backend's own signed-string vocabulary (the four static
|
|
2102
|
+
* family ids), held here so a route the signature cannot cover fails at this
|
|
2103
|
+
* boundary. `ttlS` stays within the backend's documented cap (600 default,
|
|
2104
|
+
* 3600 max) - a bound, not a preference.
|
|
2105
|
+
*/
|
|
2106
|
+
zod.z.object({
|
|
2107
|
+
op: zod.z.literal("static.sign"),
|
|
2108
|
+
route: zod.z.enum(["images", "videos", "audio", "html"]),
|
|
2109
|
+
path: zod.z.string().min(1),
|
|
2110
|
+
ttlS: zod.z.number().int().min(1).max(3600).optional()
|
|
2091
2111
|
}).strict()
|
|
2092
2112
|
]);
|
|
2093
2113
|
const desktopRequestSchema = desktopRequestUnion.superRefine(
|
|
@@ -3648,6 +3668,23 @@ function desktopEndpoint(request) {
|
|
|
3648
3668
|
...request.force !== void 0 ? { force: request.force } : {}
|
|
3649
3669
|
}
|
|
3650
3670
|
};
|
|
3671
|
+
/*
|
|
3672
|
+
* The previews' signed static URLs: NOT under `/v1/desktop/` - the core's
|
|
3673
|
+
* sign route accepts the desktop bearer OR its local access token - so an
|
|
3674
|
+
* answer here never counts as pairing evidence (`desktopAnswerProvesPairing`
|
|
3675
|
+
* reads the prefix, deliberately). `ttl_s` is omitted unless asked for, so
|
|
3676
|
+
* the core's own default (600 s) is the one number for "unspecified".
|
|
3677
|
+
*/
|
|
3678
|
+
case "static.sign":
|
|
3679
|
+
return {
|
|
3680
|
+
path: "/v1/static/sign",
|
|
3681
|
+
method: "POST",
|
|
3682
|
+
body: {
|
|
3683
|
+
route: request.route,
|
|
3684
|
+
path: request.path,
|
|
3685
|
+
ttl_s: request.ttlS
|
|
3686
|
+
}
|
|
3687
|
+
};
|
|
3651
3688
|
}
|
|
3652
3689
|
}
|
|
3653
3690
|
const MAX_PROBE_PATHS = 64;
|
|
@@ -4484,6 +4521,9 @@ const SHIPIT_RELATIVE_PATH = path$1.join(
|
|
|
4484
4521
|
const INSTALLER_TOKEN = "local-operator-update-installer";
|
|
4485
4522
|
const UPDATE_STAGING_DIR = "update-staging";
|
|
4486
4523
|
const INSTALLER_POLL_SECONDS = "0.2";
|
|
4524
|
+
const INSTALLER_FIRST_GRACE_SECONDS = 60;
|
|
4525
|
+
const INSTALLER_SECOND_GRACE_SECONDS = 20;
|
|
4526
|
+
const INSTALLER_KILL_SETTLE_SECONDS = "0.5";
|
|
4487
4527
|
const EXTRACTION_TIMEOUT_MS = 10 * 6e4;
|
|
4488
4528
|
function shipItPathInBundle(bundlePath) {
|
|
4489
4529
|
if (!bundlePath) return null;
|
|
@@ -4563,6 +4603,8 @@ function shipItStatePlist(input) {
|
|
|
4563
4603
|
};
|
|
4564
4604
|
}
|
|
4565
4605
|
function buildInstallerSpawn(input) {
|
|
4606
|
+
const firstGrace = input.graceSeconds?.first ?? INSTALLER_FIRST_GRACE_SECONDS;
|
|
4607
|
+
const secondGrace = input.graceSeconds?.second ?? INSTALLER_SECOND_GRACE_SECONDS;
|
|
4566
4608
|
const script = `#!/bin/sh
|
|
4567
4609
|
# ${INSTALLER_TOKEN}
|
|
4568
4610
|
#
|
|
@@ -4573,10 +4615,74 @@ function buildInstallerSpawn(input) {
|
|
|
4573
4615
|
# (SQRLInstallerErrorDomain Code=-9, the 2026-09-18 09:37 install). So this waits
|
|
4574
4616
|
# for the app's pid to go, and then replaces itself with the installer: one
|
|
4575
4617
|
# process, one pid for the app to record, and no shell left in the way.
|
|
4576
|
-
|
|
4618
|
+
#
|
|
4619
|
+
# THE WAIT IS BOUNDED AND LOGGED (2026-10-11): the unbounded shape waited 26 m
|
|
4620
|
+
# 49 s while the app sat alive with no window, and logged nothing anywhere. Each
|
|
4621
|
+
# stage below is bounded and writes one timestamped line to the installer's log;
|
|
4622
|
+
# the last stage starts the installer regardless, because a pid that survives
|
|
4623
|
+
# SIGKILL is a zombie and nothing more exists to wait for.
|
|
4624
|
+
app_pid=$1
|
|
4625
|
+
started=$(date +%s)
|
|
4626
|
+
log_stage() {
|
|
4627
|
+
printf '%s ${INSTALLER_TOKEN}: %s\\n' "$(date '+%Y-%m-%dT%H:%M:%S')" "$1" >>"$LO_UPDATE_INSTALLER_LOG"
|
|
4628
|
+
}
|
|
4629
|
+
elapsed() {
|
|
4630
|
+
printf '%d' "$(( $(date +%s) - started ))"
|
|
4631
|
+
}
|
|
4632
|
+
# A recycled pid is never signalled: before any kill, the target's command line
|
|
4633
|
+
# must still name the app's own executable ($LO_UPDATE_INSTALLER_APP_EXEC,
|
|
4634
|
+
# environment only - see the args comment at the callers).
|
|
4635
|
+
target_is_app() {
|
|
4636
|
+
[ -n "$LO_UPDATE_INSTALLER_APP_EXEC" ] || return 1
|
|
4637
|
+
_line=$(ps -o command= -p "$app_pid" 2>/dev/null) || return 1
|
|
4638
|
+
case "$_line" in
|
|
4639
|
+
*"$LO_UPDATE_INSTALLER_APP_EXEC"*) return 0 ;;
|
|
4640
|
+
esac
|
|
4641
|
+
return 1
|
|
4642
|
+
}
|
|
4643
|
+
start_installer() {
|
|
4644
|
+
log_stage "starting the installer ($(elapsed)s in)"
|
|
4645
|
+
exec "$LO_UPDATE_INSTALLER_SHIPIT" "$LO_UPDATE_INSTALLER_LABEL" "$LO_UPDATE_INSTALLER_STATE" >>"$LO_UPDATE_INSTALLER_LOG" 2>&1
|
|
4646
|
+
}
|
|
4647
|
+
log_stage "waiting up to ${firstGrace}s for the app to exit on its own"
|
|
4648
|
+
_end=$((started + ${firstGrace}))
|
|
4649
|
+
while kill -0 "$app_pid" 2>/dev/null; do
|
|
4650
|
+
if [ "$(date +%s)" -ge "$_end" ]; then break; fi
|
|
4577
4651
|
sleep ${INSTALLER_POLL_SECONDS}
|
|
4578
4652
|
done
|
|
4579
|
-
|
|
4653
|
+
if ! kill -0 "$app_pid" 2>/dev/null; then
|
|
4654
|
+
log_stage "the app exited on its own ($(elapsed)s in)"
|
|
4655
|
+
start_installer
|
|
4656
|
+
fi
|
|
4657
|
+
log_stage "the first grace expired with the app still there"
|
|
4658
|
+
if target_is_app; then
|
|
4659
|
+
log_stage "asking the app to stop (SIGTERM)"
|
|
4660
|
+
kill -TERM "$app_pid" 2>/dev/null
|
|
4661
|
+
_end=$(( $(date +%s) + ${secondGrace} ))
|
|
4662
|
+
while kill -0 "$app_pid" 2>/dev/null; do
|
|
4663
|
+
if [ "$(date +%s)" -ge "$_end" ]; then break; fi
|
|
4664
|
+
sleep ${INSTALLER_POLL_SECONDS}
|
|
4665
|
+
done
|
|
4666
|
+
if ! kill -0 "$app_pid" 2>/dev/null; then
|
|
4667
|
+
log_stage "the app exited after SIGTERM ($(elapsed)s in)"
|
|
4668
|
+
start_installer
|
|
4669
|
+
fi
|
|
4670
|
+
if target_is_app; then
|
|
4671
|
+
log_stage "the second grace expired; forcing the pid gone (SIGKILL)"
|
|
4672
|
+
kill -KILL "$app_pid" 2>/dev/null
|
|
4673
|
+
sleep ${INSTALLER_KILL_SETTLE_SECONDS}
|
|
4674
|
+
if kill -0 "$app_pid" 2>/dev/null; then
|
|
4675
|
+
log_stage "the pid survived SIGKILL (a zombie or unkillable); nothing left to wait for"
|
|
4676
|
+
else
|
|
4677
|
+
log_stage "the app is gone after SIGKILL ($(elapsed)s in)"
|
|
4678
|
+
fi
|
|
4679
|
+
else
|
|
4680
|
+
log_stage "target is not the app after SIGTERM; starting the installer"
|
|
4681
|
+
fi
|
|
4682
|
+
start_installer
|
|
4683
|
+
fi
|
|
4684
|
+
log_stage "target is not the app; starting the installer without signalling"
|
|
4685
|
+
start_installer
|
|
4580
4686
|
`;
|
|
4581
4687
|
return {
|
|
4582
4688
|
script,
|
|
@@ -4584,7 +4690,8 @@ exec "$LO_UPDATE_INSTALLER_SHIPIT" "$LO_UPDATE_INSTALLER_LABEL" "$LO_UPDATE_INST
|
|
|
4584
4690
|
LO_UPDATE_INSTALLER_SHIPIT: input.shipItPath,
|
|
4585
4691
|
LO_UPDATE_INSTALLER_LABEL: input.label,
|
|
4586
4692
|
LO_UPDATE_INSTALLER_STATE: input.statePath,
|
|
4587
|
-
LO_UPDATE_INSTALLER_LOG: input.logPath
|
|
4693
|
+
LO_UPDATE_INSTALLER_LOG: input.logPath,
|
|
4694
|
+
LO_UPDATE_INSTALLER_APP_EXEC: input.appExecPath
|
|
4588
4695
|
},
|
|
4589
4696
|
// `sh -c <script> <name> <pid>`: the name is $0 and the pid is $1, so the
|
|
4590
4697
|
// script text carries no path and no number that a process listing of it
|
|
@@ -4611,6 +4718,8 @@ const INSTALL_DISK_SLACK_BYTES = 256 * 1024 * 1024;
|
|
|
4611
4718
|
const DOWNLOAD_PAGE_URL = "https://local-operator.com/download";
|
|
4612
4719
|
const WATCHDOG_TIMEOUT_SECONDS = 600;
|
|
4613
4720
|
const WATCHDOG_HARD_TIMEOUT_SECONDS = 1800;
|
|
4721
|
+
const WATCHDOG_APP_WAIT_NOTICE_SECONDS = 15;
|
|
4722
|
+
const WATCHDOG_APP_WAIT_FALLBACK_SECONDS = 2 * (INSTALLER_FIRST_GRACE_SECONDS + INSTALLER_SECOND_GRACE_SECONDS);
|
|
4614
4723
|
const PENDING_INSTALL_RECENCY_MARGIN_SECONDS = 300;
|
|
4615
4724
|
const PENDING_INSTALL_RECENCY_SECONDS = WATCHDOG_HARD_TIMEOUT_SECONDS + PENDING_INSTALL_RECENCY_MARGIN_SECONDS;
|
|
4616
4725
|
const LAUNCH_HOLD_END_MARGIN_SECONDS = 60;
|
|
@@ -5346,13 +5455,15 @@ function watchdogSignals(platform) {
|
|
|
5346
5455
|
return { jobProbe: "launchctl", plistReader: "/usr/bin/plutil" };
|
|
5347
5456
|
}
|
|
5348
5457
|
function buildWatchdogPlan(input) {
|
|
5349
|
-
const signals = input.signals ?? watchdogSignals(input.platform);
|
|
5458
|
+
const signals = input.signals ?? watchdogSignals(input.platform ?? "darwin");
|
|
5350
5459
|
const timeoutSeconds = input.timeoutSeconds ?? WATCHDOG_TIMEOUT_SECONDS;
|
|
5351
5460
|
const hardTimeoutSeconds = input.hardTimeoutSeconds ?? WATCHDOG_HARD_TIMEOUT_SECONDS;
|
|
5352
5461
|
const intervalSeconds = input.intervalSeconds ?? 3;
|
|
5353
5462
|
const settleSeconds = input.settleSeconds ?? 5;
|
|
5354
5463
|
const appearSeconds = input.appearSeconds ?? 30;
|
|
5355
5464
|
const announceSeconds = input.announceSeconds ?? 5;
|
|
5465
|
+
const appWaitNoticeSeconds = input.appWaitNoticeSeconds ?? WATCHDOG_APP_WAIT_NOTICE_SECONDS;
|
|
5466
|
+
const appWaitFallbackSeconds = input.appWaitFallbackSeconds ?? WATCHDOG_APP_WAIT_FALLBACK_SECONDS;
|
|
5356
5467
|
const plistTimeoutSeconds = input.plistReadTimeoutSeconds ?? PLIST_READ_TIMEOUT_SECONDS;
|
|
5357
5468
|
const script = `#!/bin/sh
|
|
5358
5469
|
# ${WATCHDOG_TOKEN}
|
|
@@ -5450,6 +5561,27 @@ if [ -z "$APP_PID" ] || [ -z "$BUNDLE" ]; then
|
|
|
5450
5561
|
fi
|
|
5451
5562
|
now() { date +%s; }
|
|
5452
5563
|
app_running() { kill -0 "$APP_PID" 2>/dev/null; }
|
|
5564
|
+
# THE WAIT'S OWN CLOCK (2026-10-11). The script is spawned as the quit begins, so
|
|
5565
|
+
# its own start IS the wait's start - elapsed is measured from here rather than
|
|
5566
|
+
# from the marker's startedAt, which is written before the quit and would need a
|
|
5567
|
+
# new seam through two collaborators for a difference of seconds.
|
|
5568
|
+
started=$(now)
|
|
5569
|
+
# "waiting 15 s" / "waiting 2 min 30 s": elapsed in the units a person reads,
|
|
5570
|
+
# with the seconds term dropped at an exact minute ("waiting 30 min") - "30 min
|
|
5571
|
+
# 0 s" reads like a clock fault rather than a wait (round-1 D2).
|
|
5572
|
+
elapsed_text() {
|
|
5573
|
+
_seconds=$(( $(now) - started ))
|
|
5574
|
+
if [ "$_seconds" -lt 0 ]; then _seconds=0; fi
|
|
5575
|
+
if [ "$_seconds" -ge 60 ]; then
|
|
5576
|
+
if [ $((_seconds % 60)) -eq 0 ]; then
|
|
5577
|
+
printf '%d min' "$((_seconds / 60))"
|
|
5578
|
+
else
|
|
5579
|
+
printf '%d min %d s' "$((_seconds / 60))" "$((_seconds % 60))"
|
|
5580
|
+
fi
|
|
5581
|
+
else
|
|
5582
|
+
printf '%d s' "$_seconds"
|
|
5583
|
+
fi
|
|
5584
|
+
}
|
|
5453
5585
|
# Whether ANY process is this install's ShipIt, for the pid that stopped being it.
|
|
5454
5586
|
#
|
|
5455
5587
|
# The same two facts installerElsewhere requires of a line in its listing: this
|
|
@@ -5676,8 +5808,31 @@ decided() {
|
|
|
5676
5808
|
deadline=$(( $(now) + ${timeoutSeconds} ))
|
|
5677
5809
|
hard_deadline=$(( $(now) + ${hardTimeoutSeconds} ))
|
|
5678
5810
|
# 1. The install only starts once the old app has exited.
|
|
5811
|
+
#
|
|
5812
|
+
# AND THE WAIT SPEAKS, INSTEAD OF LEAVING THE PERSON IN SILENCE (2026-10-11).
|
|
5813
|
+
# The incident's app sat alive with no window for 26 m 49 s; this script was
|
|
5814
|
+
# the only process left that could say anything, and its first step was
|
|
5815
|
+
# silent until the soft bound - the person watched nothing happen and
|
|
5816
|
+
# resolved it by hand ('kill -TERM', which is what let ShipIt exec). So the
|
|
5817
|
+
# wait now says what it is waiting for and for how long, well before the
|
|
5818
|
+
# installer's own escalation signals the app
|
|
5819
|
+
# (WATCHDOG_APP_WAIT_NOTICE_SECONDS), and after the escalation has
|
|
5820
|
+
# provably failed it hands over the action that works
|
|
5821
|
+
# (WATCHDOG_APP_WAIT_FALLBACK_SECONDS). Each fires at most once; the
|
|
5822
|
+
# kill switch silences both like every other notice (notify's own guard).
|
|
5823
|
+
app_wait_notice=0
|
|
5824
|
+
app_wait_fallback=0
|
|
5679
5825
|
while app_running; do
|
|
5680
5826
|
if [ "$(now)" -ge "$deadline" ]; then break; fi
|
|
5827
|
+
_waited=$(( $(now) - started ))
|
|
5828
|
+
if [ "$app_wait_notice" -eq 0 ] && [ "$_waited" -ge ${appWaitNoticeSeconds} ]; then
|
|
5829
|
+
app_wait_notice=1
|
|
5830
|
+
notify "Waiting for Local Operator to close so the update can install (waiting $(elapsed_text)). No action is needed yet."
|
|
5831
|
+
fi
|
|
5832
|
+
if [ "$app_wait_fallback" -eq 0 ] && [ "$_waited" -ge ${appWaitFallbackSeconds} ]; then
|
|
5833
|
+
app_wait_fallback=1
|
|
5834
|
+
notify "The update is still waiting for Local Operator to close (waiting $(elapsed_text)). If nothing happens, quit Local Operator (Force Quit if it does not respond) and leave it closed until it opens again by itself."
|
|
5835
|
+
fi
|
|
5681
5836
|
sleep ${intervalSeconds}
|
|
5682
5837
|
done
|
|
5683
5838
|
# 1b. The app is gone and an install is at work, so the window that just
|
|
@@ -5752,7 +5907,16 @@ while :; do
|
|
|
5752
5907
|
if [ "$(now)" -ge "$deadline" ]; then
|
|
5753
5908
|
if [ "$signal_known" -eq 1 ] && install_live; then
|
|
5754
5909
|
holding=1
|
|
5755
|
-
|
|
5910
|
+
# THE SOFT BOUND BRANCHES TOO (round-1 UX U4): it fires while the app
|
|
5911
|
+
# may still be RUNNING, where "keep it closed" describes a state the
|
|
5912
|
+
# person is not in. App running -> the wait/fallback copy with a fresh
|
|
5913
|
+
# elapsed reading; app gone -> the stay-closed copy, which is true there
|
|
5914
|
+
# (an install this app spawned is live and the swap still needs quiet).
|
|
5915
|
+
if app_running; then
|
|
5916
|
+
notify "The update is still waiting for Local Operator to close (waiting $(elapsed_text)). If nothing happens, quit Local Operator (Force Quit if it does not respond) and leave it closed until it opens again by itself."
|
|
5917
|
+
else
|
|
5918
|
+
notify "The update is still installing. Keep Local Operator closed; it will open again when the install finishes."
|
|
5919
|
+
fi
|
|
5756
5920
|
fi
|
|
5757
5921
|
break
|
|
5758
5922
|
fi
|
|
@@ -5780,7 +5944,20 @@ if [ "$holding" -eq 1 ]; then
|
|
|
5780
5944
|
while :; do
|
|
5781
5945
|
if decided; then break; fi
|
|
5782
5946
|
if [ "$(now)" -ge "$hard_deadline" ]; then
|
|
5783
|
-
|
|
5947
|
+
# TRUTHFUL ON BOTH ARMS (2026-10-11). The notice that used to stand
|
|
5948
|
+
# here promised "Local Operator is opening again" unconditionally, and
|
|
5949
|
+
# the path below with the app STILL RUNNING ('if app_running; then
|
|
5950
|
+
# exit 0') is exactly where that promise is false - the watchdog
|
|
5951
|
+
# exits without launching, because launching into a running app is
|
|
5952
|
+
# what aborts a swap. So the app-still-running arm now says the true
|
|
5953
|
+
# thing and hands over the action that resolves it, the same fallback
|
|
5954
|
+
# the wait's own notices use; the app-gone arm keeps the notice the
|
|
5955
|
+
# panel's copy expects (UX U10).
|
|
5956
|
+
if app_running; then
|
|
5957
|
+
notify "The update is still waiting for Local Operator to close (waiting $(elapsed_text)). If nothing happens, quit Local Operator (Force Quit if it does not respond) and leave it closed until it opens again by itself."
|
|
5958
|
+
else
|
|
5959
|
+
notify "The update is taking longer than expected. Local Operator is opening again so you are not left without it — if it says the update is still installing, quit it and leave it closed until the install finishes."
|
|
5960
|
+
fi
|
|
5784
5961
|
break
|
|
5785
5962
|
fi
|
|
5786
5963
|
sleep ${intervalSeconds}
|
|
@@ -35442,6 +35619,32 @@ function rememberPickedDirectory(kind, filePaths, directoryPicked = false) {
|
|
|
35442
35619
|
if (!first) return;
|
|
35443
35620
|
lastPickedDirectory.set(kind, directoryPicked ? first : path$1.dirname(first));
|
|
35444
35621
|
}
|
|
35622
|
+
const QUIT_EXIT_DEADLINE_MARGIN_MS = 5e3;
|
|
35623
|
+
function quitExitDeadlineMs(input) {
|
|
35624
|
+
return SESSION_COOKIE_QUIT_BUDGET_MS + input.quitCleanupFailsafeMs + QUIT_EXIT_DEADLINE_MARGIN_MS;
|
|
35625
|
+
}
|
|
35626
|
+
function createQuitExitDeadline(input) {
|
|
35627
|
+
let timer = null;
|
|
35628
|
+
let stage = "";
|
|
35629
|
+
return {
|
|
35630
|
+
arm(next) {
|
|
35631
|
+
if (timer) return;
|
|
35632
|
+
stage = next;
|
|
35633
|
+
timer = setTimeout(() => {
|
|
35634
|
+
timer = null;
|
|
35635
|
+
input.onFire(stage);
|
|
35636
|
+
}, input.timeoutMs);
|
|
35637
|
+
timer.unref();
|
|
35638
|
+
},
|
|
35639
|
+
phase(next) {
|
|
35640
|
+
stage = next;
|
|
35641
|
+
},
|
|
35642
|
+
release() {
|
|
35643
|
+
if (timer) clearTimeout(timer);
|
|
35644
|
+
timer = null;
|
|
35645
|
+
}
|
|
35646
|
+
};
|
|
35647
|
+
}
|
|
35445
35648
|
function createQuitState() {
|
|
35446
35649
|
let quitting = false;
|
|
35447
35650
|
return {
|
|
@@ -36440,6 +36643,9 @@ const GLOBAL_UPDATE_TIMEOUT_MS = 15 * 60 * 1e3;
|
|
|
36440
36643
|
const SOURCE_REBUILD_TIMEOUT_MS = 30 * 60 * 1e3;
|
|
36441
36644
|
const UPDATE_RESTART_STOP_GRACE_MS = 5e3;
|
|
36442
36645
|
const BUNDLE_ID_REGEX = /^[\w.-]+$/;
|
|
36646
|
+
const APP_UPDATE_CACHE_DIR_NAME_REGEX = /^updaterCacheDirName:[ \t]*(\S+)[ \t]*$/m;
|
|
36647
|
+
const CACHE_DIR_NAME_OK_REGEX = /^[A-Za-z0-9._-]+$/;
|
|
36648
|
+
const UPDATER_CACHE_PENDING_FILE_REGEX = /\.zip$/i;
|
|
36443
36649
|
const INSTALL_IN_FLIGHT_RECHECK_MS = 1e4;
|
|
36444
36650
|
const LAUNCHCTL_NOT_LOADED_REGEX = /could not find|no such process/i;
|
|
36445
36651
|
const APP_OWNED_PUBLISH_HEADROOM_BYTES = 450 * 1024 * 1024;
|
|
@@ -37446,6 +37652,7 @@ class UpdateService {
|
|
|
37446
37652
|
);
|
|
37447
37653
|
this.reapWatchdog(outcome.marker, true);
|
|
37448
37654
|
this.reapStagedUpdateTrees();
|
|
37655
|
+
this.reapUpdaterCache();
|
|
37449
37656
|
clearPendingInstallMarker(this.markerDir());
|
|
37450
37657
|
this.retireSupersededInstallRecord();
|
|
37451
37658
|
this.pendingInstallSucceeded = {
|
|
@@ -37460,6 +37667,7 @@ class UpdateService {
|
|
|
37460
37667
|
);
|
|
37461
37668
|
this.reapWatchdog(outcome.marker, true);
|
|
37462
37669
|
this.reapStagedUpdateTrees();
|
|
37670
|
+
this.reapUpdaterCache();
|
|
37463
37671
|
clearPendingInstallMarker(this.markerDir());
|
|
37464
37672
|
this.retireSupersededInstallRecord();
|
|
37465
37673
|
return;
|
|
@@ -37512,6 +37720,7 @@ class UpdateService {
|
|
|
37512
37720
|
this.reapWatchdog(outcome.marker, false);
|
|
37513
37721
|
this.reapFailedInstallLeftovers();
|
|
37514
37722
|
this.reapStagedUpdateTrees();
|
|
37723
|
+
this.reapUpdaterCache();
|
|
37515
37724
|
this.schedulePendingInstallFailureDelivery();
|
|
37516
37725
|
}
|
|
37517
37726
|
}
|
|
@@ -38643,6 +38852,16 @@ ${heal.removed.join("\n")}`,
|
|
|
38643
38852
|
startInstaller(handoff) {
|
|
38644
38853
|
const plan = buildInstallerSpawn({
|
|
38645
38854
|
appPid: process.pid,
|
|
38855
|
+
/*
|
|
38856
|
+
* The path the spawner's identity check matches before it signals
|
|
38857
|
+
* anything: this process's own executable. `process.execPath` is
|
|
38858
|
+
* `/Applications/Local Operator.app/Contents/MacOS/Local Operator`
|
|
38859
|
+
* in the packaged app, and the running main process's command line
|
|
38860
|
+
* starts with exactly that path - which is the question
|
|
38861
|
+
* `ps -o command= -p <pid>` answers. It travels in the environment,
|
|
38862
|
+
* never in the script text (see the callee's own note).
|
|
38863
|
+
*/
|
|
38864
|
+
appExecPath: process.execPath,
|
|
38646
38865
|
shipItPath: handoff.shipItPath,
|
|
38647
38866
|
label: handoff.label,
|
|
38648
38867
|
statePath: handoff.statePath,
|
|
@@ -38728,6 +38947,146 @@ ${heal.removed.join("\n")}`,
|
|
|
38728
38947
|
}
|
|
38729
38948
|
}
|
|
38730
38949
|
}
|
|
38950
|
+
/**
|
|
38951
|
+
* The updater's own cache directory for this app, or null when it cannot be
|
|
38952
|
+
* resolved.
|
|
38953
|
+
*
|
|
38954
|
+
* WHY THE APP NEEDS THIS PATH AT ALL (2026-10-11 cache hygiene): nothing ever
|
|
38955
|
+
* clears `~/Library/Caches/<updater cache dir>/` - measured residue after one
|
|
38956
|
+
* successful install: `update.zip` (~180 MB), `pending/<artifact>.zip` (~180
|
|
38957
|
+
* MB, the SAME bytes) and `pending/update-info.json`, all three left behind by
|
|
38958
|
+
* a download that has already been used or replaced. electron-updater clears
|
|
38959
|
+
* `pending/` on a failed DOWNLOAD and never clears `update.zip` - by design,
|
|
38960
|
+
* because `update.zip` is the differential base its next download patches
|
|
38961
|
+
* against (`MacUpdater`'s `canDifferentialDownload`), so deleting it buys disk
|
|
38962
|
+
* at the cost of a full ~180 MB re-download. `reapUpdaterCache` below is the
|
|
38963
|
+
* bounded-retention answer; this resolver is how it finds the directory.
|
|
38964
|
+
*
|
|
38965
|
+
* TWO SOURCES, in order. The download helper is authoritative for a download
|
|
38966
|
+
* THIS run performed (the same private surface `ensureStagedArtifactVerified`
|
|
38967
|
+
* already reads). It does not survive a restart, and the reap runs at
|
|
38968
|
+
* start-up, so the fallback reads the name electron-updater itself would
|
|
38969
|
+
* read: `updaterCacheDirName` from `app-update.yml` beside the packaged app,
|
|
38970
|
+
* under the base cache path its own adapter computes (`~/Library/Caches` on
|
|
38971
|
+
* macOS). ONE key on ONE line is parsed rather than adding a YAML dependency
|
|
38972
|
+
* on a package that is a transitive dependency of the updater rather than
|
|
38973
|
+
* ours.
|
|
38974
|
+
*
|
|
38975
|
+
* A DELETION ROOT IS NEVER TAKEN FROM THE MARKER or any other mutable record:
|
|
38976
|
+
* a corrupted record would then choose what gets deleted. Null means
|
|
38977
|
+
* "declined to reap", never a guess.
|
|
38978
|
+
*/
|
|
38979
|
+
updaterCacheRoot() {
|
|
38980
|
+
const helper = this.downloadHelper();
|
|
38981
|
+
if (helper?.cacheDirForPendingUpdate) {
|
|
38982
|
+
return path$1.dirname(helper.cacheDirForPendingUpdate);
|
|
38983
|
+
}
|
|
38984
|
+
try {
|
|
38985
|
+
const config = fs.readFileSync(
|
|
38986
|
+
path$1.join(process.resourcesPath, "app-update.yml"),
|
|
38987
|
+
"utf8"
|
|
38988
|
+
);
|
|
38989
|
+
const name = config.match(APP_UPDATE_CACHE_DIR_NAME_REGEX)?.[1];
|
|
38990
|
+
if (!name || !CACHE_DIR_NAME_OK_REGEX.test(name)) return null;
|
|
38991
|
+
return path$1.join(electron.app.getPath("home"), "Library", "Caches", name);
|
|
38992
|
+
} catch {
|
|
38993
|
+
return null;
|
|
38994
|
+
}
|
|
38995
|
+
}
|
|
38996
|
+
/**
|
|
38997
|
+
* Reduce the updater's cache to at most ONE artifact's worth, once an install
|
|
38998
|
+
* is settled (2026-10-11).
|
|
38999
|
+
*
|
|
39000
|
+
* THE RETENTION RULE, and its reasoning. After every settled verdict the cache
|
|
39001
|
+
* is DEDUPED, not emptied: `update.zip` and `pending/<artifact>.zip` are the
|
|
39002
|
+
* same bytes, and only one of them has future value - `update.zip`, the
|
|
39003
|
+
* differential base the next download patches against (`MacUpdater` copies
|
|
39004
|
+
* every download to it, and falls back to a FULL download without it). So:
|
|
39005
|
+
* when `update.zip` exists, the pending artifact and the `update-info.json`
|
|
39006
|
+
* that describes it go (the bytes survive in the base), and when it does not,
|
|
39007
|
+
* nothing is deleted - there is nothing to dedupe, and the pending artifact is
|
|
39008
|
+
* the only copy. The retention window is bounded by the next download, which
|
|
39009
|
+
* overwrites the base in place; there is deliberately NO day-count constant,
|
|
39010
|
+
* because a clock cannot tell a live delta base from a dead one and would
|
|
39011
|
+
* either delete the base the next update needs or count forever on an idle
|
|
39012
|
+
* machine. ROLLBACK STORY: nothing here is a rollback artifact - `update.zip`
|
|
39013
|
+
* cannot be installed by itself, and the app's reinstall and recovery paths
|
|
39014
|
+
* re-fetch from the feed; the retained copy exists for the next update's
|
|
39015
|
+
* DELTA (worth ~180 MB of transfer), not for restoring anything older.
|
|
39016
|
+
*
|
|
39017
|
+
* SAFETY RAILS, re-asked at the point of deletion rather than trusted from the
|
|
39018
|
+
* caller: the three branches that call this have already decided the install
|
|
39019
|
+
* is not in flight, but the machine is asked ONCE more - a live marker, a
|
|
39020
|
+
* running installer, or a relaunch watchdog that is still OURS all mean
|
|
39021
|
+
* something else may still be reading this cache, and the reap stands down
|
|
39022
|
+
* with a log line. A registered-but-idle ShipIt job is NOT one of those
|
|
39023
|
+
* signals (the 0.17.0 lessons: the job outlives finished installs by design),
|
|
39024
|
+
* and the in-flight branch above NEVER calls here.
|
|
39025
|
+
*/
|
|
39026
|
+
reapUpdaterCache() {
|
|
39027
|
+
const note = (message) => logger.info(message, LogFileType.UPDATE_SERVICE);
|
|
39028
|
+
const marker = readPendingInstallMarker(this.markerDir());
|
|
39029
|
+
const liveness = this.installLiveness(marker);
|
|
39030
|
+
if (liveness.installerRunning || marker !== null && isInstallInFlight({
|
|
39031
|
+
marker,
|
|
39032
|
+
jobState: liveness.jobState,
|
|
39033
|
+
installerRunning: liveness.installerRunning
|
|
39034
|
+
})) {
|
|
39035
|
+
note(
|
|
39036
|
+
"Not reaping the updater cache: the machine still reports an install in flight."
|
|
39037
|
+
);
|
|
39038
|
+
return;
|
|
39039
|
+
}
|
|
39040
|
+
const watchdogPid = marker?.watchdogPid ?? null;
|
|
39041
|
+
if (watchdogPid != null) {
|
|
39042
|
+
const commandLine = readCommandOutput("/bin/ps", [
|
|
39043
|
+
"-o",
|
|
39044
|
+
"command=",
|
|
39045
|
+
"-p",
|
|
39046
|
+
String(watchdogPid)
|
|
39047
|
+
]);
|
|
39048
|
+
if (watchdogIsOurs({
|
|
39049
|
+
alive: commandLine != null,
|
|
39050
|
+
commandLine,
|
|
39051
|
+
installSucceeded: true
|
|
39052
|
+
})) {
|
|
39053
|
+
note(
|
|
39054
|
+
`Not reaping the updater cache while relaunch watchdog ${watchdogPid} is still running.`
|
|
39055
|
+
);
|
|
39056
|
+
return;
|
|
39057
|
+
}
|
|
39058
|
+
}
|
|
39059
|
+
const root = this.updaterCacheRoot();
|
|
39060
|
+
if (!root) {
|
|
39061
|
+
note("Updater cache not reaped: its directory could not be resolved.");
|
|
39062
|
+
return;
|
|
39063
|
+
}
|
|
39064
|
+
const base = path$1.join(root, "update.zip");
|
|
39065
|
+
if (!fs.existsSync(base)) {
|
|
39066
|
+
note(
|
|
39067
|
+
"Updater cache left alone: no differential base to dedupe the pending artifact against."
|
|
39068
|
+
);
|
|
39069
|
+
return;
|
|
39070
|
+
}
|
|
39071
|
+
const pendingDir = path$1.join(root, "pending");
|
|
39072
|
+
if (!fs.existsSync(pendingDir)) return;
|
|
39073
|
+
for (const entry of fs.readdirSync(pendingDir)) {
|
|
39074
|
+
if (!UPDATER_CACHE_PENDING_FILE_REGEX.test(entry) && entry !== "update-info.json")
|
|
39075
|
+
continue;
|
|
39076
|
+
const target2 = path$1.join(pendingDir, entry);
|
|
39077
|
+
try {
|
|
39078
|
+
fs.rmSync(target2, { force: true });
|
|
39079
|
+
note(
|
|
39080
|
+
`Reaped the updater cache's duplicate of the differential base: ${target2}.`
|
|
39081
|
+
);
|
|
39082
|
+
} catch (error) {
|
|
39083
|
+
logger.warn(
|
|
39084
|
+
`Could not reap ${target2}: ${String(error)}`,
|
|
39085
|
+
LogFileType.UPDATE_SERVICE
|
|
39086
|
+
);
|
|
39087
|
+
}
|
|
39088
|
+
}
|
|
39089
|
+
}
|
|
38731
39090
|
/**
|
|
38732
39091
|
* Both of the machine's liveness answers for this marker.
|
|
38733
39092
|
*
|
|
@@ -43859,6 +44218,7 @@ const backendInstaller = new BackendInstaller({
|
|
|
43859
44218
|
// (`./relaunch-pending` owns why scheduling happens only at the terminal).
|
|
43860
44219
|
onQuitCancelled: () => {
|
|
43861
44220
|
quitState.cancel();
|
|
44221
|
+
quitExitDeadline.release();
|
|
43862
44222
|
relaunchPending.clear();
|
|
43863
44223
|
}
|
|
43864
44224
|
});
|
|
@@ -44178,6 +44538,22 @@ electron.app.whenReady().then(async () => {
|
|
|
44178
44538
|
process.on(signal, () => endHeadlessRun(`received ${signal}`));
|
|
44179
44539
|
}
|
|
44180
44540
|
}
|
|
44541
|
+
if (windowLaunch.mode !== "headless") {
|
|
44542
|
+
const onSigtermDuringQuit = () => {
|
|
44543
|
+
if (!quitState.isQuitting()) {
|
|
44544
|
+
process.removeListener("SIGTERM", onSigtermDuringQuit);
|
|
44545
|
+
process.kill(process.pid, "SIGTERM");
|
|
44546
|
+
return;
|
|
44547
|
+
}
|
|
44548
|
+
logger.info(
|
|
44549
|
+
"Received SIGTERM during a quit; exiting so the quit reaches its terminal (an update installer may be waiting on this pid).",
|
|
44550
|
+
LogFileType.BACKEND
|
|
44551
|
+
);
|
|
44552
|
+
scheduleReopenAtQuitTerminal();
|
|
44553
|
+
electron.app.exit(0);
|
|
44554
|
+
};
|
|
44555
|
+
process.on("SIGTERM", onSigtermDuringQuit);
|
|
44556
|
+
}
|
|
44181
44557
|
electron.app.on("browser-window-created", (_, window) => {
|
|
44182
44558
|
utils.optimizer.watchWindowShortcuts(window);
|
|
44183
44559
|
});
|
|
@@ -45184,7 +45560,22 @@ const scheduleReopenAtQuitTerminal = () => {
|
|
|
45184
45560
|
}
|
|
45185
45561
|
});
|
|
45186
45562
|
};
|
|
45563
|
+
const QUIT_EXIT_DEADLINE_MS = quitExitDeadlineMs({
|
|
45564
|
+
quitCleanupFailsafeMs: QUIT_CLEANUP_FAILSAFE_MS
|
|
45565
|
+
});
|
|
45566
|
+
const quitExitDeadline = createQuitExitDeadline({
|
|
45567
|
+
timeoutMs: QUIT_EXIT_DEADLINE_MS,
|
|
45568
|
+
onFire: (firedInStage) => {
|
|
45569
|
+
logger.error(
|
|
45570
|
+
`The quit did not finish within ${QUIT_EXIT_DEADLINE_MS} ms (stalled ${firedInStage}); exiting with failure so the process cannot sit alive with no window`,
|
|
45571
|
+
LogFileType.BACKEND
|
|
45572
|
+
);
|
|
45573
|
+
scheduleReopenAtQuitTerminal();
|
|
45574
|
+
electron.app.exit(1);
|
|
45575
|
+
}
|
|
45576
|
+
});
|
|
45187
45577
|
electron.app.on("will-quit", (event) => {
|
|
45578
|
+
quitExitDeadline.phase("will-quit");
|
|
45188
45579
|
viewerEndpoint?.close();
|
|
45189
45580
|
viewerRecord?.stop();
|
|
45190
45581
|
scheduleReopenAtQuitTerminal();
|
|
@@ -45224,6 +45615,7 @@ const holdQuitForSessionCookieSnapshot = createSessionCookieQuitHold({
|
|
|
45224
45615
|
});
|
|
45225
45616
|
electron.app.on("before-quit", async (event) => {
|
|
45226
45617
|
quitState.begin();
|
|
45618
|
+
quitExitDeadline.arm("before the session-cookie hold");
|
|
45227
45619
|
electron.globalShortcut.unregisterAll();
|
|
45228
45620
|
miniViewRegistrar?.dispose();
|
|
45229
45621
|
quickSendWatcher?.stop();
|
|
@@ -45233,6 +45625,7 @@ electron.app.on("before-quit", async (event) => {
|
|
|
45233
45625
|
armHeadlessExitDeadline("the app is quitting");
|
|
45234
45626
|
}
|
|
45235
45627
|
if (await holdQuitForSessionCookieSnapshot(event)) return;
|
|
45628
|
+
quitExitDeadline.phase("after the session-cookie hold");
|
|
45236
45629
|
logger.info("App is about to quit", LogFileType.BACKEND);
|
|
45237
45630
|
if (!backendService.isOwnedCleanupComplete()) {
|
|
45238
45631
|
try {
|
|
@@ -45253,6 +45646,7 @@ electron.app.on("before-quit", async (event) => {
|
|
|
45253
45646
|
}
|
|
45254
45647
|
activeUpdateService?.quitForInFlightInstall("app quit");
|
|
45255
45648
|
void stopBrowserHost();
|
|
45649
|
+
quitExitDeadline.phase("awaiting the window close");
|
|
45256
45650
|
});
|
|
45257
45651
|
process.on("exit", () => {
|
|
45258
45652
|
try {
|
package/out/preload/index.js
CHANGED
|
@@ -2113,6 +2113,26 @@ const desktopRequestUnion = zod.z.discriminatedUnion("op", [
|
|
|
2113
2113
|
*/
|
|
2114
2114
|
keys: zod.z.array(zod.z.string().min(1).max(512)).max(256).optional(),
|
|
2115
2115
|
force: zod.z.boolean().optional()
|
|
2116
|
+
}).strict(),
|
|
2117
|
+
/*
|
|
2118
|
+
* THE PREVIEWS' SIGNED STATIC URLS (file-serving RFC § 4, Phase A adoption).
|
|
2119
|
+
* The renderer cannot read the core's serve record (no filesystem, no record
|
|
2120
|
+
* store), so the previews ask MAIN - which already holds the desktop bearer -
|
|
2121
|
+
* to mint a short-lived signed URL for one static route + path. The route is
|
|
2122
|
+
* public in Phase A; this op is what lets the previews keep working when a
|
|
2123
|
+
* later core phase gates it, and a core that predates the route answers 404,
|
|
2124
|
+
* which the previews read as their fallback signal, not an error.
|
|
2125
|
+
*
|
|
2126
|
+
* `route` is the backend's own signed-string vocabulary (the four static
|
|
2127
|
+
* family ids), held here so a route the signature cannot cover fails at this
|
|
2128
|
+
* boundary. `ttlS` stays within the backend's documented cap (600 default,
|
|
2129
|
+
* 3600 max) - a bound, not a preference.
|
|
2130
|
+
*/
|
|
2131
|
+
zod.z.object({
|
|
2132
|
+
op: zod.z.literal("static.sign"),
|
|
2133
|
+
route: zod.z.enum(["images", "videos", "audio", "html"]),
|
|
2134
|
+
path: zod.z.string().min(1),
|
|
2135
|
+
ttlS: zod.z.number().int().min(1).max(3600).optional()
|
|
2116
2136
|
}).strict()
|
|
2117
2137
|
]);
|
|
2118
2138
|
desktopRequestUnion.superRefine(
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{e as o,c as b,g as v,k as O,h as P,j as p,l as w,m as A,n as x,t as c,o as N}from"./_baseUniq-
|
|
1
|
+
import{e as o,c as b,g as v,k as O,h as P,j as p,l as w,m as A,n as x,t as c,o as N}from"./_baseUniq-DKeCGkCd.js";import{as as g,aa as y,at as E,au as F,av as M,aw as I,ax as _,ay as $,az as B,aA as T}from"./mermaid.core-DNS50-NS.js";var S=/\s/;function z(n){for(var r=n.length;r--&&S.test(n.charAt(r)););return r}var G=/^\s+/;function H(n){return n&&n.slice(0,z(n)+1).replace(G,"")}var m=NaN,L=/^[-+]0x[0-9a-f]+$/i,R=/^0b[01]+$/i,q=/^0o[0-7]+$/i,C=parseInt;function K(n){if(typeof n=="number")return n;if(o(n))return m;if(g(n)){var r=typeof n.valueOf=="function"?n.valueOf():n;n=g(r)?r+"":r}if(typeof n!="string")return n===0?n:+n;n=H(n);var t=R.test(n);return t||q.test(n)?C(n.slice(2),t?2:8):L.test(n)?m:+n}var W=1/0,X=17976931348623157e292;function Y(n){if(!n)return n===0?n:0;if(n=K(n),n===W||n===-1/0){var r=n<0?-1:1;return r*X}return n===n?n:0}function D(n){var r=Y(n),t=r%1;return r===r?t?r-t:r:0}function fn(n){var r=n==null?0:n.length;return r?b(n):[]}var l=Object.prototype,J=l.hasOwnProperty,dn=y(function(n,r){n=Object(n);var t=-1,e=r.length,a=e>2?r[2]:void 0;for(a&&E(r[0],r[1],a)&&(e=1);++t<e;)for(var f=r[t],i=F(f),s=-1,d=i.length;++s<d;){var u=i[s],h=n[u];(h===void 0||M(h,l[u])&&!J.call(n,u))&&(n[u]=f[u])}return n});function un(n){var r=n==null?0:n.length;return r?n[r-1]:void 0}function Q(n){return function(r,t,e){var a=Object(r);if(!I(r)){var f=v(t);r=O(r),t=function(s){return f(a[s],s,a)}}var i=n(r,t,e);return i>-1?a[f?r[i]:i]:void 0}}var U=Math.max;function Z(n,r,t){var e=n==null?0:n.length;if(!e)return-1;var a=t==null?0:D(t);return a<0&&(a=U(e+a,0)),P(n,v(r),a)}var hn=Q(Z);function V(n,r){var t=-1,e=I(n)?Array(n.length):[];return p(n,function(a,f,i){e[++t]=r(a,f,i)}),e}function gn(n,r){var t=_(n)?w:V;return t(n,v(r))}var j=Object.prototype,k=j.hasOwnProperty;function nn(n,r){return n!=null&&k.call(n,r)}function vn(n,r){return n!=null&&A(n,r,nn)}function rn(n,r){return n<r}function tn(n,r,t){for(var e=-1,a=n.length;++e<a;){var f=n[e],i=r(f);if(i!=null&&(s===void 0?i===i&&!o(i):t(i,s)))var s=i,d=f}return d}function mn(n){return n&&n.length?tn(n,$,rn):void 0}function an(n,r,t,e){if(!g(n))return n;r=x(r,n);for(var a=-1,f=r.length,i=f-1,s=n;s!=null&&++a<f;){var d=c(r[a]),u=t;if(d==="__proto__"||d==="constructor"||d==="prototype")return n;if(a!=i){var h=s[d];u=void 0,u===void 0&&(u=g(h)?h:B(r[a+1])?[]:{})}T(s,d,u),s=s[d]}return n}function on(n,r,t){for(var e=-1,a=r.length,f={};++e<a;){var i=r[e],s=N(n,i);t(s,i)&&an(f,x(i,n),s)}return f}export{rn as a,tn as b,V as c,on as d,mn as e,fn as f,hn as g,vn as h,dn as i,D as j,un as l,gn as m,Y as t};
|